Merge "Collect embedded TaskFragment window changes" into tm-qpr-dev
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7a88a057..5ca8b05 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3920,6 +3920,12 @@
* that has been started. This is sent as a foreground
* broadcast, since it is part of a visible user interaction; be as quick
* as possible when handling it.
+ *
+ * <p>
+ * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+ * received. For example, the user could have been removed, started or stopped already,
+ * regardless of which broadcast you receive. Because of that, receivers should always check
+ * the current state of the user.
* @hide
*/
public static final String ACTION_USER_STARTED =
@@ -3937,6 +3943,12 @@
* {@link #ACTION_USER_STOPPING}. It is <b>not</b> generally safe to use with
* other user state broadcasts since those are foreground broadcasts so can
* execute in a different order.
+ *
+ * <p>
+ * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+ * received. For example, the user could have been removed, started or stopped already,
+ * regardless of which broadcast you receive. Because of that, receivers should always check
+ * the current state of the user.
* @hide
*/
public static final String ACTION_USER_STARTING =
@@ -3955,6 +3967,11 @@
* {@link #ACTION_USER_STARTING}. It is <b>not</b> generally safe to use with
* other user state broadcasts since those are foreground broadcasts so can
* execute in a different order.
+ * <p>
+ * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+ * received. For example, the user could have been removed, started or stopped already,
+ * regardless of which broadcast you receive. Because of that, receivers should always check
+ * the current state of the user.
* @hide
*/
public static final String ACTION_USER_STOPPING =
@@ -3967,6 +3984,12 @@
* specific package. This is only sent to registered receivers, not manifest
* receivers. It is sent to all running users <em>except</em> the one that
* has just been stopped (which is no longer running).
+ *
+ * <p>
+ * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+ * received. For example, the user could have been removed, started or stopped already,
+ * regardless of which broadcast you receive. Because of that, receivers should always check
+ * the current state of the user.
* @hide
*/
@TestApi
@@ -3999,6 +4022,12 @@
* It is sent to all running users.
* You must hold
* {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
+ *
+ * <p>
+ * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+ * received. For example, the user could have been removed, started or stopped already,
+ * regardless of which broadcast you receive. Because of that, receivers should always check
+ * the current state of the user.
* @hide
*/
@SystemApi
@@ -4009,6 +4038,12 @@
* Broadcast Action: Sent when the credential-encrypted private storage has
* become unlocked for the target user. This is only sent to registered
* receivers, not manifest receivers.
+ *
+ * <p>
+ * <b>Note:</b> The user's actual state might have changed by the time the broadcast is
+ * received. For example, the user could have been removed, started or stopped already,
+ * regardless of which broadcast you receive. Because of that, receivers should always check
+ * the current state of the user.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 7e355d9..e845ffa 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -934,6 +934,12 @@
* you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
* Otherwise, this method might throw an exception or return {@code null}.
*
+ * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #getParcelable(String)} instead.
+ *
* @param key a String, or {@code null}
* @param clazz The type of the object expected
* @return a Parcelable value, or {@code null}
@@ -990,6 +996,13 @@
* you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
* Otherwise, this method might throw an exception or return {@code null}.
*
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #getParcelableArray(String)} instead.
+ *
* @param key a String, or {@code null}
* @param clazz The type of the items inside the array. This is only verified when unparceling.
* @return a Parcelable[] value, or {@code null}
@@ -1054,6 +1067,13 @@
* you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
* Otherwise, this method might throw an exception or return {@code null}.
*
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #getParcelableArrayList(String)} instead.
+ *
* @param key a String, or {@code null}
* @param clazz The type of the items inside the array list. This is only verified when
* unparceling.
@@ -1105,6 +1125,13 @@
* <li>The object is not of type {@code clazz}.
* </ul>
*
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #getSparseParcelableArray(String)} instead.
+ *
* @param key a String, or null
* @param clazz The type of the items inside the sparse array. This is only verified when
* unparceling.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2664f05..3d70138 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3235,6 +3235,13 @@
* Same as {@link #readList(List, ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readList(List, ClassLoader)} instead.
+ *
* @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
* is not an instance of that class or any of its children classes or there was an error
* trying to instantiate an element.
@@ -3463,6 +3470,13 @@
* Same as {@link #readArrayList(ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readArrayList(ClassLoader)} instead.
+ *
* @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
* is not an instance of that class or any of its children classes or there was an error
* trying to instantiate an element.
@@ -3497,6 +3511,13 @@
* Same as {@link #readArray(ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readArray(ClassLoader)} instead.
+ *
* @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
* is not an instance of that class or any of its children classes or there was an error
* trying to instantiate an element.
@@ -3530,6 +3551,13 @@
* Same as {@link #readSparseArray(ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readSparseArray(ClassLoader)} instead.
+ *
* @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
* is not an instance of that class or any of its children classes or there was an error
* trying to instantiate an element.
@@ -3847,6 +3875,13 @@
* Same as {@link #readParcelableList(List, ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readParcelableList(List, ClassLoader)} instead.
+ *
* @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
* is not an instance of that class or any of its children classes or there was an error
* trying to instantiate an element.
@@ -4744,6 +4779,12 @@
* Same as {@link #readParcelable(ClassLoader)} but accepts {@code clazz} parameter as the type
* required for each item.
*
+ * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readParcelable(ClassLoader)} instead.
+ *
* @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
* is not an instance of that class or any of its children classes or there was an error
* trying to instantiate an element.
@@ -4812,6 +4853,12 @@
* Same as {@link #readParcelableCreator(ClassLoader)} but accepts {@code clazz} parameter
* as the required type.
*
+ * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readParcelableCreator(ClassLoader) instead.
+ *
* @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
* is not an instance of that class or any of its children classes or there there was an error
* trying to read the {@link Parcelable.Creator}.
@@ -4939,6 +4986,12 @@
* Same as {@link #readParcelableArray(ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readParcelableArray(ClassLoader)} instead.
+ *
* @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
* is not an instance of that class or any of its children classes or there was an error
* trying to instantiate an element.
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c102ad3..c198098 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -349,20 +349,6 @@
return insets;
}
- // TODO: Remove this once the task bar is treated as navigation bar.
- public Insets calculateInsetsWithInternalTypes(Rect frame, @InternalInsetsType int[] types,
- boolean ignoreVisibility) {
- Insets insets = Insets.NONE;
- for (int i = types.length - 1; i >= 0; i--) {
- InsetsSource source = mSources[types[i]];
- if (source == null) {
- continue;
- }
- insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
- }
- return insets;
- }
-
public Insets calculateInsets(Rect frame, @InsetsType int types,
InsetsVisibilities overrideVisibilities) {
Insets insets = Insets.NONE;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e673041..ec6b4ac 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -284,7 +284,7 @@
* @hide
*/
public static final boolean LOCAL_LAYOUT =
- SystemProperties.getBoolean("persist.debug.local_layout", true);
+ SystemProperties.getBoolean("persist.debug.local_layout", false);
/**
* Set this system property to true to force the view hierarchy to render
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index fbabf52..a66b405 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -63,6 +63,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -146,6 +147,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Supplier;
/**
@@ -281,6 +283,7 @@
private long mQueriedSharingShortcutsTimeMs;
private int mCurrAvailableWidth = 0;
+ private Insets mLastAppliedInsets = null;
private int mLastNumberOfChildren = -1;
private int mMaxTargetsPerRow = 1;
@@ -1619,6 +1622,8 @@
if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
}
+ mPersonalAppPredictor = null;
+ mWorkAppPredictor = null;
}
@Override // ResolverListCommunicator
@@ -2531,7 +2536,11 @@
|| gridAdapter.calculateChooserTargetWidth(availableWidth)
|| recyclerView.getAdapter() == null
|| availableWidth != mCurrAvailableWidth;
+
+ boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets);
+
if (isLayoutUpdated
+ || insetsChanged
|| mLastNumberOfChildren != recyclerView.getChildCount()) {
mCurrAvailableWidth = availableWidth;
if (isLayoutUpdated) {
@@ -2552,7 +2561,7 @@
return;
}
- if (mLastNumberOfChildren == recyclerView.getChildCount()) {
+ if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) {
return;
}
@@ -2563,6 +2572,7 @@
int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
mEnterTransitionAnimationDelegate.markOffsetCalculated();
+ mLastAppliedInsets = mSystemWindowInsets;
});
}
}
@@ -3055,7 +3065,12 @@
mChooserMultiProfilePagerAdapter.setupContainerPadding(
getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container));
}
- return super.onApplyWindowInsets(v, insets);
+
+ WindowInsets result = super.onApplyWindowInsets(v, insets);
+ if (mResolverDrawerLayout != null) {
+ mResolverDrawerLayout.requestLayout();
+ }
+ return result;
}
private void setHorizontalScrollingEnabled(boolean enabled) {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 1ec5325..e57b90a 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -43,6 +43,7 @@
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
@@ -86,7 +87,7 @@
private final ChooserActivityLogger mChooserActivityLogger;
private int mNumShortcutResults = 0;
- private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
+ private final Map<TargetInfo, AsyncTask> mIconLoaders = new HashMap<>();
private boolean mApplySharingAppLimits;
// Reserve spots for incoming direct share targets by adding placeholders
@@ -104,6 +105,8 @@
private AppPredictor mAppPredictor;
private AppPredictor.Callback mAppPredictorCallback;
+ private LoadDirectShareIconTaskProvider mTestLoadDirectShareTaskProvider;
+
// For pinned direct share labels, if the text spans multiple lines, the TextView will consume
// the full width, even if the characters actually take up less than that. Measure the actual
// line widths and constrain the View's width based upon that so that the pin doesn't end up
@@ -240,7 +243,6 @@
mListViewDataChanged = false;
}
-
private void createPlaceHolders() {
mNumShortcutResults = 0;
mServiceTargets.clear();
@@ -265,31 +267,25 @@
return;
}
- if (!(info instanceof DisplayResolveInfo)) {
- holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
- holder.bindIcon(info);
-
- if (info instanceof SelectableTargetInfo) {
- // direct share targets should append the application name for a better readout
- DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
- CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
- CharSequence extendedInfo = info.getExtendedInfo();
- String contentDescription = String.join(" ", info.getDisplayLabel(),
- extendedInfo != null ? extendedInfo : "", appName);
- holder.updateContentDescription(contentDescription);
- }
- } else {
+ if (info instanceof DisplayResolveInfo) {
DisplayResolveInfo dri = (DisplayResolveInfo) info;
holder.bindLabel(dri.getDisplayLabel(), dri.getExtendedInfo(), alwaysShowSubLabel());
- LoadIconTask task = mIconLoaders.get(dri);
- if (task == null) {
- task = new LoadIconTask(dri, holder);
- mIconLoaders.put(dri, task);
- task.execute();
+ startDisplayResolveInfoIconLoading(holder, dri);
+ } else {
+ holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
+
+ if (info instanceof SelectableTargetInfo) {
+ SelectableTargetInfo selectableInfo = (SelectableTargetInfo) info;
+ // direct share targets should append the application name for a better readout
+ DisplayResolveInfo rInfo = selectableInfo.getDisplayResolveInfo();
+ CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
+ CharSequence extendedInfo = selectableInfo.getExtendedInfo();
+ String contentDescription = String.join(" ", selectableInfo.getDisplayLabel(),
+ extendedInfo != null ? extendedInfo : "", appName);
+ holder.updateContentDescription(contentDescription);
+ startSelectableTargetInfoIconLoading(holder, selectableInfo);
} else {
- // The holder was potentially changed as the underlying items were
- // reshuffled, so reset the target holder
- task.setViewHolder(holder);
+ holder.bindIcon(info);
}
}
@@ -330,6 +326,32 @@
}
}
+ private void startDisplayResolveInfoIconLoading(ViewHolder holder, DisplayResolveInfo info) {
+ LoadIconTask task = (LoadIconTask) mIconLoaders.get(info);
+ if (task == null) {
+ task = new LoadIconTask(info, holder);
+ mIconLoaders.put(info, task);
+ task.execute();
+ } else {
+ // The holder was potentially changed as the underlying items were
+ // reshuffled, so reset the target holder
+ task.setViewHolder(holder);
+ }
+ }
+
+ private void startSelectableTargetInfoIconLoading(
+ ViewHolder holder, SelectableTargetInfo info) {
+ LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info);
+ if (task == null) {
+ task = mTestLoadDirectShareTaskProvider == null
+ ? new LoadDirectShareIconTask(info)
+ : mTestLoadDirectShareTaskProvider.get();
+ mIconLoaders.put(info, task);
+ task.loadIcon();
+ }
+ task.setViewHolder(holder);
+ }
+
void updateAlphabeticalList() {
new AsyncTask<Void, Void, List<DisplayResolveInfo>>() {
@Override
@@ -344,7 +366,7 @@
Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
for (DisplayResolveInfo info : allTargets) {
String resolvedTarget = info.getResolvedComponentName().getPackageName()
- + '#' + info.getDisplayLabel();
+ + '#' + info.getDisplayLabel();
DisplayResolveInfo multiDri = consolidated.get(resolvedTarget);
if (multiDri == null) {
consolidated.put(resolvedTarget, info);
@@ -353,7 +375,7 @@
} else {
// create consolidated target from the single DisplayResolveInfo
MultiDisplayResolveInfo multiDisplayResolveInfo =
- new MultiDisplayResolveInfo(resolvedTarget, multiDri);
+ new MultiDisplayResolveInfo(resolvedTarget, multiDri);
multiDisplayResolveInfo.addTarget(info);
consolidated.put(resolvedTarget, multiDisplayResolveInfo);
}
@@ -740,10 +762,24 @@
}
/**
+ * An alias for onBindView to use with unit tests.
+ */
+ @VisibleForTesting
+ public void testViewBind(View view, TargetInfo info, int position) {
+ onBindView(view, info, position);
+ }
+
+ @VisibleForTesting
+ public void setTestLoadDirectShareTaskProvider(LoadDirectShareIconTaskProvider provider) {
+ mTestLoadDirectShareTaskProvider = provider;
+ }
+
+ /**
* Necessary methods to communicate between {@link ChooserListAdapter}
* and {@link ChooserActivity}.
*/
- interface ChooserListCommunicator extends ResolverListCommunicator {
+ @VisibleForTesting
+ public interface ChooserListCommunicator extends ResolverListCommunicator {
int getMaxRankedTargets();
@@ -751,4 +787,59 @@
boolean isSendAction(Intent targetIntent);
}
+
+ /**
+ * Loads direct share targets icons.
+ */
+ @VisibleForTesting
+ public class LoadDirectShareIconTask extends AsyncTask<Void, Void, Void> {
+ private final SelectableTargetInfo mTargetInfo;
+ private ViewHolder mViewHolder;
+
+ private LoadDirectShareIconTask(SelectableTargetInfo targetInfo) {
+ mTargetInfo = targetInfo;
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ mTargetInfo.loadIcon();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void arg) {
+ if (mViewHolder != null) {
+ mViewHolder.bindIcon(mTargetInfo);
+ notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Specifies a view holder that will be updated when the task is completed.
+ */
+ public void setViewHolder(ViewHolder viewHolder) {
+ mViewHolder = viewHolder;
+ mViewHolder.bindIcon(mTargetInfo);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * An alias for execute to use with unit tests.
+ */
+ public void loadIcon() {
+ execute();
+ }
+ }
+
+ /**
+ * An interface for the unit tests to override icon loading task creation
+ */
+ @VisibleForTesting
+ public interface LoadDirectShareIconTaskProvider {
+ /**
+ * Provides an instance of the task.
+ * @return
+ */
+ LoadDirectShareIconTask get();
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 66fff5c..3a3baa7 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -834,7 +834,11 @@
void onHandlePackagesChanged(ResolverListAdapter listAdapter);
}
- static class ViewHolder {
+ /**
+ * A view holder.
+ */
+ @VisibleForTesting
+ public static class ViewHolder {
public View itemView;
public Drawable defaultItemViewBackground;
@@ -842,7 +846,8 @@
public TextView text2;
public ImageView icon;
- ViewHolder(View view) {
+ @VisibleForTesting
+ public ViewHolder(View view) {
itemView = view;
defaultItemViewBackground = view.getBackground();
text = (TextView) view.findViewById(com.android.internal.R.id.text1);
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 264e4f7..37eab40 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -37,6 +37,7 @@
import android.text.SpannableStringBuilder;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.ChooserActivity;
import com.android.internal.app.ResolverActivity;
import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
@@ -59,8 +60,11 @@
private final String mDisplayLabel;
private final PackageManager mPm;
private final SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator;
+ @GuardedBy("this")
+ private ShortcutInfo mShortcutInfo;
private Drawable mBadgeIcon = null;
private CharSequence mBadgeContentDescription;
+ @GuardedBy("this")
private Drawable mDisplayIcon;
private final Intent mFillInIntent;
private final int mFillInFlags;
@@ -78,6 +82,7 @@
mModifiedScore = modifiedScore;
mPm = mContext.getPackageManager();
mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator;
+ mShortcutInfo = shortcutInfo;
mIsPinned = shortcutInfo != null && shortcutInfo.isPinned();
if (sourceInfo != null) {
final ResolveInfo ri = sourceInfo.getResolveInfo();
@@ -92,8 +97,6 @@
}
}
}
- // TODO(b/121287224): do this in the background thread, and only for selected targets
- mDisplayIcon = getChooserTargetIconDrawable(chooserTarget, shortcutInfo);
if (sourceInfo != null) {
mBackupResolveInfo = null;
@@ -118,7 +121,10 @@
mChooserTarget = other.mChooserTarget;
mBadgeIcon = other.mBadgeIcon;
mBadgeContentDescription = other.mBadgeContentDescription;
- mDisplayIcon = other.mDisplayIcon;
+ synchronized (other) {
+ mShortcutInfo = other.mShortcutInfo;
+ mDisplayIcon = other.mDisplayIcon;
+ }
mFillInIntent = fillInIntent;
mFillInFlags = flags;
mModifiedScore = other.mModifiedScore;
@@ -141,6 +147,25 @@
return mSourceInfo;
}
+ /**
+ * Load display icon, if needed.
+ */
+ public void loadIcon() {
+ ShortcutInfo shortcutInfo;
+ Drawable icon;
+ synchronized (this) {
+ shortcutInfo = mShortcutInfo;
+ icon = mDisplayIcon;
+ }
+ if (icon == null && shortcutInfo != null) {
+ icon = getChooserTargetIconDrawable(mChooserTarget, shortcutInfo);
+ synchronized (this) {
+ mDisplayIcon = icon;
+ mShortcutInfo = null;
+ }
+ }
+ }
+
private Drawable getChooserTargetIconDrawable(ChooserTarget target,
@Nullable ShortcutInfo shortcutInfo) {
Drawable directShareIcon = null;
@@ -270,7 +295,7 @@
}
@Override
- public Drawable getDisplayIcon(Context context) {
+ public synchronized Drawable getDisplayIcon(Context context) {
return mDisplayIcon;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 483efd86c..d5f40e0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1567,8 +1567,7 @@
<bool name="config_enableIdleScreenBrightnessMode">false</bool>
<!-- Array of desired screen brightness in nits corresponding to the lux values
- in the config_autoBrightnessLevels array. As with config_screenBrightnessMinimumNits and
- config_screenBrightnessMaximumNits, the display brightness is defined as the measured
+ in the config_autoBrightnessLevels array. The display brightness is defined as the measured
brightness of an all-white image.
If this is defined then:
@@ -1589,7 +1588,7 @@
<array name="config_autoBrightnessDisplayValuesNitsIdle">
</array>
- <!-- Array of output values for button backlight corresponding to the luX values
+ <!-- Array of output values for button backlight corresponding to the lux values
in the config_autoBrightnessLevels array. This array should have size one greater
than the size of the config_autoBrightnessLevels array.
The brightness values must be between 0 and 255 and be non-decreasing.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 71919ff..e773a9c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -79,6 +79,11 @@
<java-symbol type="id" name="deny_button" />
<java-symbol type="id" name="description" />
<java-symbol type="id" name="divider" />
+ <java-symbol type="id" name="drag" />
+ <java-symbol type="id" name="profile_pager" />
+ <java-symbol type="id" name="chooser_header" />
+ <java-symbol type="id" name="content_preview_container" />
+ <java-symbol type="id" name="profile_tabhost" />
<java-symbol type="id" name="edit_query" />
<java-symbol type="id" name="edittext_container" />
<java-symbol type="id" name="expand_activities_button" />
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
new file mode 100644
index 0000000..f027e0b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.service.chooser.ChooserTarget
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.R
+import com.android.internal.app.chooser.SelectableTargetInfo
+import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator
+import com.android.server.testutils.any
+import com.android.server.testutils.mock
+import com.android.server.testutils.whenever
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+class ChooserListAdapterTest {
+ private val packageManager = mock<PackageManager> {
+ whenever(resolveActivity(any(), anyInt())).thenReturn(mock())
+ }
+ private val context = InstrumentationRegistry.getInstrumentation().getContext()
+ private val resolverListController = mock<ResolverListController>()
+ private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> {
+ whenever(maxRankedTargets).thenReturn(0)
+ }
+ private val selectableTargetInfoCommunicator =
+ mock<SelectableTargetInfoCommunicator> {
+ whenever(targetIntent).thenReturn(mock())
+ }
+ private val chooserActivityLogger = mock<ChooserActivityLogger>()
+
+ private val testSubject = ChooserListAdapter(
+ context,
+ emptyList(),
+ emptyArray(),
+ emptyList(),
+ false,
+ resolverListController,
+ chooserListCommunicator,
+ selectableTargetInfoCommunicator,
+ packageManager,
+ chooserActivityLogger
+ )
+
+ @Test
+ fun testDirectShareTargetLoadingIconIsStarted() {
+ val view = createView()
+ val viewHolder = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolder
+ val targetInfo = createSelectableTargetInfo()
+ val iconTask = mock<ChooserListAdapter.LoadDirectShareIconTask> {
+ doNothing().whenever(this).loadIcon()
+ }
+ testSubject.setTestLoadDirectShareTaskProvider(
+ mock {
+ whenever(get()).thenReturn(iconTask)
+ }
+ )
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ verify(iconTask, times(1)).loadIcon()
+ verify(iconTask, times(1)).setViewHolder(viewHolder)
+ }
+
+ @Test
+ fun testOnlyOneTaskPerTarget() {
+ val view = createView()
+ val viewHolderOne = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolderOne
+ val targetInfo = createSelectableTargetInfo()
+ val iconTaskOne = mock<ChooserListAdapter.LoadDirectShareIconTask> {
+ doNothing().whenever(this).loadIcon()
+ }
+ val testTaskProvider = mock<ChooserListAdapter.LoadDirectShareIconTaskProvider> {
+ whenever(get()).thenReturn(iconTaskOne)
+ }
+ testSubject.setTestLoadDirectShareTaskProvider(
+ testTaskProvider
+ )
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ val viewHolderTwo = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolderTwo
+ whenever(testTaskProvider.get()).thenReturn(mock())
+
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ verify(iconTaskOne, times(1)).loadIcon()
+ verify(iconTaskOne, times(1)).setViewHolder(viewHolderOne)
+ verify(iconTaskOne, times(1)).setViewHolder(viewHolderTwo)
+ verify(testTaskProvider, times(1)).get()
+ }
+
+ private fun createSelectableTargetInfo(): SelectableTargetInfo =
+ SelectableTargetInfo(
+ context,
+ null,
+ createChooserTarget(),
+ 1f,
+ selectableTargetInfoCommunicator,
+ null
+ )
+
+ private fun createChooserTarget(): ChooserTarget =
+ ChooserTarget(
+ "Title",
+ null,
+ 1f,
+ ComponentName("package", "package.Class"),
+ Bundle()
+ )
+
+ private fun createView(): View {
+ val view = FrameLayout(context)
+ TextView(context).apply {
+ id = R.id.text1
+ view.addView(this)
+ }
+ TextView(context).apply {
+ id = R.id.text2
+ view.addView(this)
+ }
+ ImageView(context).apply {
+ id = R.id.icon
+ view.addView(this)
+ }
+ return view
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index d5d4935..b5409b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,6 +16,7 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -695,15 +696,19 @@
/**
* Create a {@link WindowContainerTransaction} to clear task bounds.
*
+ * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
+ * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+ *
* @param displayId display id for tasks that will have bounds cleared
* @return {@link WindowContainerTransaction} with pending operations to clear bounds
*/
- public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) {
+ public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
WindowContainerTransaction wct = new WindowContainerTransaction();
for (int i = 0; i < mTasks.size(); i++) {
RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if (taskInfo.displayId == displayId) {
+ if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
+ == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
taskInfo.token, taskInfo);
wct.setBounds(taskInfo.token, null);
@@ -715,17 +720,21 @@
/**
* Create a {@link WindowContainerTransaction} to clear task level freeform setting.
*
+ * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
+ * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+ *
* @param displayId display id for tasks that will have windowing mode reset to {@link
* WindowConfiguration#WINDOWING_MODE_UNDEFINED}
* @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
*/
- public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) {
+ public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
WindowContainerTransaction wct = new WindowContainerTransaction();
for (int i = 0; i < mTasks.size(); i++) {
RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
if (taskInfo.displayId == displayId
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
"clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 295035f..e7533bcb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -73,14 +73,15 @@
WindowContainerTransaction wct = new WindowContainerTransaction();
// Reset freeform windowing mode that is set per task level (tasks should inherit
// container value)
- wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */);
+ wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
+ true /* transfer */);
int targetWindowingMode;
if (active) {
targetWindowingMode = WINDOWING_MODE_FREEFORM;
} else {
targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
// Clear any resized bounds
- wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId),
+ wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
true /* transfer */);
}
wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b46eff6..297c79e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1306,6 +1306,12 @@
return;
}
+ if (mLeash == null || !mLeash.isValid()) {
+ Log.e(TAG, String.format("scheduleFinishResizePip with null leash! mState=%d",
+ mPipTransitionState.getTransitionState()));
+ return;
+ }
+
finishResize(createFinishResizeSurfaceTransaction(destinationBounds), destinationBounds,
direction, -1);
if (updateBoundsCallback != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 8d405f4..199007d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -589,17 +589,17 @@
return;
}
- // The surfaces of splitting tasks were placed with window bounds when preparing the
- // transition, so update divider surface separately.
- final RemoteAnimationTarget dividerTarget = getDividerBarLegacyTarget();
- mSplitLayout.getRefDividerBounds(mTempRect1);
- t.setLayer(dividerTarget.leash, Integer.MAX_VALUE)
- .setPosition(dividerTarget.leash, mTempRect1.left, mTempRect1.top);
- setDividerVisibility(true, t);
+ // Wrap the divider bar into non-apps target to animate together.
+ nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ getDividerBarLegacyTarget());
+ updateSurfaceBounds(mSplitLayout, t, false);
+ setDividerVisibility(true, t);
for (int i = 0; i < apps.length; ++i) {
if (apps[i].mode == MODE_OPENING) {
t.show(apps[i].leash);
+ // Reset the surface position of the opening app to prevent double-offset.
+ t.setPosition(apps[i].leash, 0, 0);
}
}
t.apply();
@@ -614,9 +614,8 @@
};
Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
try {
- adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
- ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
- dividerTarget), wrapCallback);
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, wrapCallback);
} catch (RemoteException e) {
Slog.e(TAG, "Error starting remote animation", e);
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index f50097d..7680f4d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -43,7 +43,7 @@
/**
* Test entering pip while changing orientation (from app in landscape to pip window in portrait)
*
- * To run this test: `atest EnterPipToOtherOrientationTest:EnterPipToOtherOrientationTest`
+ * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
*
* Actions:
* Launch [testApp] on a fixed portrait orientation
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index b29c436..7cbace5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,6 +16,8 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -637,26 +639,22 @@
}
@Test
- public void testPrepareClearBoundsForTasks() {
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED);
- task1.displayId = 1;
+ public void testPrepareClearBoundsForStandardTasks() {
MockToken token1 = new MockToken();
- task1.token = token1.token();
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
mOrganizer.onTaskAppeared(task1, null);
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED);
- task2.displayId = 1;
MockToken token2 = new MockToken();
- task2.token = token2.token();
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
mOrganizer.onTaskAppeared(task2, null);
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED);
- otherDisplayTask.displayId = 2;
MockToken otherDisplayToken = new MockToken();
- otherDisplayTask.token = otherDisplayToken.token();
+ RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
+ otherDisplayToken);
+ otherDisplayTask.displayId = 2;
mOrganizer.onTaskAppeared(otherDisplayTask, null);
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForTasks(1);
+ WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
assertEquals(wct.getChanges().size(), 2);
Change boundsChange1 = wct.getChanges().get(token1.binder());
@@ -673,26 +671,40 @@
}
@Test
- public void testPrepareClearFreeformForTasks() {
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM);
- task1.displayId = 1;
+ public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
MockToken token1 = new MockToken();
- task1.token = token1.token();
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
mOrganizer.onTaskAppeared(task1, null);
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
- task2.displayId = 1;
MockToken token2 = new MockToken();
- task2.token = token2.token();
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
+ task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
mOrganizer.onTaskAppeared(task2, null);
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM);
- otherDisplayTask.displayId = 2;
+ WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
+
+ // Only clear bounds for task1
+ assertEquals(1, wct.getChanges().size());
+ assertNotNull(wct.getChanges().get(token1.binder()));
+ }
+
+ @Test
+ public void testPrepareClearFreeformForStandardTasks() {
+ MockToken token1 = new MockToken();
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
+ mOrganizer.onTaskAppeared(task1, null);
+
+ MockToken token2 = new MockToken();
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
+ mOrganizer.onTaskAppeared(task2, null);
+
MockToken otherDisplayToken = new MockToken();
- otherDisplayTask.token = otherDisplayToken.token();
+ RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
+ otherDisplayToken);
+ otherDisplayTask.displayId = 2;
mOrganizer.onTaskAppeared(otherDisplayTask, null);
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForTasks(1);
+ WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
// Only task with freeform windowing mode and the right display should be updated
assertEquals(wct.getChanges().size(), 1);
@@ -701,6 +713,24 @@
assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
}
+ @Test
+ public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
+ MockToken token1 = new MockToken();
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
+ mOrganizer.onTaskAppeared(task1, null);
+
+ MockToken token2 = new MockToken();
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
+ task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
+ mOrganizer.onTaskAppeared(task2, null);
+
+ WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
+
+ // Only clear freeform for task1
+ assertEquals(1, wct.getChanges().size());
+ assertNotNull(wct.getChanges().get(token1.binder()));
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
@@ -708,6 +738,14 @@
return taskInfo;
}
+ private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
+ RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
+ taskInfo.displayId = 1;
+ taskInfo.token = token.token();
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ return taskInfo;
+ }
+
private static class MockToken {
private final WindowContainerToken mToken;
private final IBinder mBinder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 7afef8b..ef532e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -88,8 +88,8 @@
WindowContainerTransaction taskWct = new WindowContainerTransaction();
MockToken taskMockToken = new MockToken();
taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
- taskWct);
+ when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
+ mContext.getDisplayId())).thenReturn(taskWct);
// Create a fake WCT to simulate setting display windowing mode to freeform
WindowContainerTransaction displayWct = new WindowContainerTransaction();
@@ -126,15 +126,15 @@
WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
MockToken taskWmMockToken = new MockToken();
taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
- taskWmWct);
+ when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
+ mContext.getDisplayId())).thenReturn(taskWmWct);
// Create a fake WCT to simulate clearing task bounds
WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
MockToken taskBoundsMockToken = new MockToken();
taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
- when(mShellTaskOrganizer.prepareClearBoundsForTasks(mContext.getDisplayId())).thenReturn(
- taskBoundsWct);
+ when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
+ mContext.getDisplayId())).thenReturn(taskBoundsWct);
// Create a fake WCT to simulate setting display windowing mode to fullscreen
WindowContainerTransaction displayWct = new WindowContainerTransaction();
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 3a8e559..687e4dd 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -179,7 +179,7 @@
void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) {
int fast_i = (mNumFastRects - 1) * 4;
int janky_i = (mNumJankyRects - 1) * 4;
- ;
+
for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
continue;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 976117b..75d3ff7 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -512,9 +512,19 @@
ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
- const auto drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
- &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
- mLightInfo, mRenderNodes, &(profiler()));
+ IRenderPipeline::DrawResult drawResult;
+ {
+ // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
+ // or it can lead to memory corruption.
+ // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
+ // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
+ // the thread we're primarily concerned about being responsive, this being too broad
+ // shouldn't pose a performance issue.
+ std::scoped_lock lock(mFrameMetricsReporterMutex);
+ drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
+ &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
+ mLightInfo, mRenderNodes, &(profiler()));
+ }
uint64_t frameCompleteNr = getFrameNumber();
@@ -754,11 +764,11 @@
FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
if (frameInfo != nullptr) {
+ std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
gpuCompleteTime, frameInfo->get(FrameInfoIndex::CommandSubmissionCompleted));
- std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber,
surfaceControlId);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 387bae1..5e66972 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -3,5 +3,7 @@
hughchen@google.com
timhypeng@google.com
robertluo@google.com
+changbetty@google.com
+songferngwang@google.com
# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index b416738..39b4b8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -78,6 +78,9 @@
* Config the MobileStatusTracker to start or stop monitoring platform signals.
*/
public void setListening(boolean listening) {
+ if (mListening == listening) {
+ return;
+ }
mListening = listening;
if (listening) {
mPhone.registerTelephonyCallback(mReceiverHandler::post, mTelephonyCallback);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ff4c748..182f49c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -240,11 +240,9 @@
plugins: ["dagger2-compiler"],
}
-// Opt-in config for optimizing the SystemUI target using R8.
-// Enabled via `export SYSTEMUI_OPTIMIZE_JAVA=true`, or explicitly in Make via
-// the `SOONG_CONFIG_ANDROID_SYSTEMUI_OPTIMIZE_JAVA` variable.
-// TODO(b/203472868): Enable optimizations by default after stabilizing and
-// building out retrace infrastructure.
+// Opt-out config for optimizing the SystemUI target using R8.
+// Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
+// `SYSTEMUI_OPTIMIZE_JAVA := false`.
soong_config_module_type {
name: "systemui_optimized_java_defaults",
module_type: "java_defaults",
diff --git a/packages/SystemUI/res/drawable/ic_present_to_all.xml b/packages/SystemUI/res/drawable/ic_present_to_all.xml
new file mode 100644
index 0000000..d6c9bbe
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_present_to_all.xml
@@ -0,0 +1,25 @@
+ <!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v14c0,1.11 0.89,2 2,2h18c1.11,0 2,-0.89 2,-2L23,5c0,-1.11 -0.89,-2 -2,-2zM21,19.02L3,19.02L3,4.98h18v14.04zM8,12l4,-4 4,4 -1.41,1.41L13,11.83L13,16h-2v-4.17l-1.59,1.59L8,12z"/>
+ </vector>
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
new file mode 100644
index 0000000..4ad6849
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.internal.widget.ResolverDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ androidprv:maxCollapsedHeight="0dp"
+ androidprv:maxCollapsedHeightSmall="56dp"
+ androidprv:maxWidth="@*android:dimen/chooser_width"
+ android:id="@*android:id/contentPanel">
+
+ <LinearLayout
+ android:id="@*android:id/chooser_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_alwaysShow="true"
+ android:gravity="center"
+ android:elevation="0dp"
+ android:background="@*android:drawable/bottomsheet_background">
+
+ <ImageView
+ android:id="@*android:id/icon"
+ android:layout_width="@dimen/media_projection_app_selector_icon_size"
+ android:layout_height="@dimen/media_projection_app_selector_icon_size"
+ android:layout_marginTop="@*android:dimen/chooser_edge_margin_normal"
+ android:layout_marginBottom="@*android:dimen/chooser_edge_margin_normal"
+ android:importantForAccessibility="no"
+ android:tint="?android:attr/textColorPrimary"/>
+
+ <TextView android:id="@*android:id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:gravity="center"
+ android:paddingBottom="@*android:dimen/chooser_view_spacing"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@*android:id/content_preview_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+
+ <TabHost
+ android:id="@*android:id/profile_tabhost"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:background="?android:attr/colorBackground">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TabWidget
+ android:id="@*android:id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ </TabWidget>
+ <FrameLayout
+ android:id="@*android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.internal.app.ResolverViewPager
+ android:id="@*android:id/profile_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
+
+</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e3be365..e5e369e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1440,6 +1440,8 @@
<dimen name="fgs_manager_list_top_spacing">12dp</dimen>
+ <dimen name="media_projection_app_selector_icon_size">32dp</dimen>
+
<!-- Dream overlay related dimensions -->
<dimen name="dream_overlay_status_bar_height">60dp</dimen>
<dimen name="dream_overlay_status_bar_margin">40dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b33cea7..df6486a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1644,8 +1644,9 @@
<!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] -->
<string name="tile_unavailable">Unavailable</string>
- <!-- The tile in quick settings is disabled by a device administration policy [CHAR LIMIT=32] -->
- <string name="tile_disabled">Disabled</string>
+ <!-- Accessibility text for the click action on a tile that is disabled by policy. This will
+ be used following "Double-tap to..." [CHAR LIMIT=NONE] -->
+ <string name="accessibility_tile_disabled_by_policy_action_description">learn more</string>
<!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
<string name="nav_bar">Navigation bar</string>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
new file mode 100644
index 0000000..2e391c7
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.graphics.Color
+import android.view.View
+import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
+import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.*
+
+/**
+ * A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
+ */
+class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+
+ private val colorsRule = MaterialYouColorsRule()
+ private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+ private val screenshotRule =
+ ScreenshotTestRule(
+ SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ )
+ private val delegateRule =
+ RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
+ private val matcher = UnitTestBitmapMatcher
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegateRule.apply(base, description)
+ }
+
+ /**
+ * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
+ * the context of [emulationSpec].
+ */
+ fun screenshotTest(goldenIdentifier: String, view: View) {
+ view.removeElevationRecursively()
+
+ ScreenshotRuleAsserter.Builder(screenshotRule)
+ .setScreenshotProvider { view.toBitmap() }
+ .withMatcher(matcher)
+ .build()
+ .assertGoldenImage(goldenIdentifier)
+ }
+
+ /**
+ * Compare the content of the [activity] with the golden image identified by [goldenIdentifier]
+ * in the context of [emulationSpec].
+ */
+ fun activityScreenshotTest(
+ goldenIdentifier: String,
+ activity: Activity,
+ ) {
+ val rootView = activity.window.decorView
+
+ // Hide system bars, remove insets, focus and make sure device-specific cutouts
+ // don't affect screenshots
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ val window = activity.window
+ window.setDecorFitsSystemWindows(false)
+ WindowInsetsControllerCompat(window, rootView).apply {
+ hide(WindowInsetsCompat.Type.systemBars())
+ systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ }
+
+ window.statusBarColor = Color.TRANSPARENT
+ window.navigationBarColor = Color.TRANSPARENT
+ window.attributes =
+ window.attributes.apply {
+ layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+ }
+
+ rootView.removeInsetsRecursively()
+ activity.currentFocus?.clearFocus()
+ }
+
+ screenshotTest(goldenIdentifier, rootView)
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
new file mode 100644
index 0000000..98e9aaf
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.content.Intent
+import androidx.core.app.AppComponentFactory
+
+class TestAppComponentFactory : AppComponentFactory() {
+
+ init {
+ instance = this
+ }
+
+ private val overrides: MutableMap<String, () -> Activity> = hashMapOf()
+
+ fun clearOverrides() {
+ overrides.clear()
+ }
+
+ fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) {
+ overrides[activity.name] = provider
+ }
+
+ override fun instantiateActivityCompat(
+ cl: ClassLoader,
+ className: String,
+ intent: Intent?
+ ): Activity {
+ return overrides
+ .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) }
+ .invoke()
+ }
+
+ companion object {
+
+ private var instance: TestAppComponentFactory? = null
+
+ fun getInstance(): TestAppComponentFactory =
+ instance
+ ?: error(
+ "TestAppComponentFactory is not initialized, " +
+ "did you specify it in the manifest?"
+ )
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
new file mode 100644
index 0000000..b84d26a
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.util.children
+import android.view.WindowInsets
+
+/**
+ * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to
+ * disable it for any view in the hierarchy.
+ */
+fun View.removeElevationRecursively() {
+ this.elevation = 0f
+ (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively)
+}
+
+/**
+ * Different devices could have different insets (e.g. different height of the navigation bar or
+ * taskbar). This method dispatches empty insets to the whole view hierarchy and removes
+ * the original listener, so the views won't receive real insets.
+ */
+fun View.removeInsetsRecursively() {
+ this.dispatchApplyWindowInsets(WindowInsets.CONSUMED)
+ this.setOnApplyWindowInsetsListener(null)
+ (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively)
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
index c609e6f..cdedc64 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -1,10 +1,12 @@
package com.android.systemui.testing.screenshot
+import android.annotation.WorkerThread
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Bitmap
import android.graphics.Canvas
+import android.graphics.HardwareRenderer
import android.graphics.Rect
import android.os.Build
import android.os.Handler
@@ -19,8 +21,13 @@
import androidx.concurrent.futures.ResolvableFuture
import androidx.test.annotation.ExperimentalTestApi
import androidx.test.core.internal.os.HandlerExecutor
+import androidx.test.espresso.Espresso
import androidx.test.platform.graphics.HardwareRendererCompat
+import com.google.common.util.concurrent.FutureCallback
+import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.runBlocking
/*
* This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
@@ -62,6 +69,47 @@
}
/**
+ * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of
+ * [captureToBitmap].
+ */
+@WorkerThread
+@ExperimentalTestApi
+@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+fun View.toBitmap(window: Window? = null): Bitmap {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ error("toBitmap() can't be called from the main thread")
+ }
+
+ if (!HardwareRenderer.isDrawingEnabled()) {
+ error("Hardware rendering is not enabled")
+ }
+
+ // Make sure we are idle.
+ Espresso.onIdle()
+
+ val mainExecutor = context.mainExecutor
+ return runBlocking {
+ suspendCoroutine { continuation ->
+ Futures.addCallback(
+ captureToBitmap(window),
+ object : FutureCallback<Bitmap> {
+ override fun onSuccess(result: Bitmap) {
+ continuation.resumeWith(Result.success(result))
+ }
+
+ override fun onFailure(t: Throwable) {
+ continuation.resumeWith(Result.failure(t))
+ }
+ },
+ // We know that we are not on the main thread, so we can block the current
+ // thread and wait for the result in the main thread.
+ mainExecutor,
+ )
+ }
+ }
+}
+
+/**
* Trigger a redraw of the given view.
*
* Should only be called on UI thread.
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 47e2d2c..0b0595f 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -19,21 +19,13 @@
import android.app.Activity
import android.app.Dialog
import android.graphics.Bitmap
-import android.graphics.HardwareRenderer
-import android.os.Looper
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.Window
import androidx.activity.ComponentActivity
-import androidx.test.espresso.Espresso
import androidx.test.ext.junit.rules.ActivityScenarioRule
-import com.google.common.util.concurrent.FutureCallback
-import com.google.common.util.concurrent.Futures
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
@@ -89,6 +81,8 @@
// Elevation/shadows is not deterministic when doing hardware rendering, so we disable
// it for any view in the hierarchy.
window.decorView.removeElevationRecursively()
+
+ activity.currentFocus?.clearFocus()
}
// We call onActivity again because it will make sure that our Activity is done measuring,
@@ -150,53 +144,11 @@
}
}
- private fun View.removeElevationRecursively() {
- this.elevation = 0f
-
- if (this is ViewGroup) {
- repeat(childCount) { i -> getChildAt(i).removeElevationRecursively() }
- }
- }
-
private fun Dialog.toBitmap(): Bitmap {
val window = window
return window.decorView.toBitmap(window)
}
- private fun View.toBitmap(window: Window? = null): Bitmap {
- if (Looper.getMainLooper() == Looper.myLooper()) {
- error("toBitmap() can't be called from the main thread")
- }
-
- if (!HardwareRenderer.isDrawingEnabled()) {
- error("Hardware rendering is not enabled")
- }
-
- // Make sure we are idle.
- Espresso.onIdle()
-
- val mainExecutor = context.mainExecutor
- return runBlocking {
- suspendCoroutine { continuation ->
- Futures.addCallback(
- captureToBitmap(window),
- object : FutureCallback<Bitmap> {
- override fun onSuccess(result: Bitmap?) {
- continuation.resumeWith(Result.success(result!!))
- }
-
- override fun onFailure(t: Throwable) {
- continuation.resumeWith(Result.failure(t))
- }
- },
- // We know that we are not on the main thread, so we can block the current
- // thread and wait for the result in the main thread.
- mainExecutor,
- )
- }
- }
- }
-
enum class Mode(val layoutParams: LayoutParams) {
WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 0ea1965..919b71b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -52,6 +52,7 @@
val becauseCannotSkipBouncer: Boolean,
val biometricSettingEnabledForUser: Boolean,
val bouncerFullyShown: Boolean,
+ val bouncerIsOrWillShow: Boolean,
val faceAuthenticated: Boolean,
val faceDisabled: Boolean,
val goingToSleep: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 167d8af..cb88f32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2494,7 +2494,7 @@
// on bouncer if both fp and fingerprint are enrolled.
final boolean awakeKeyguardExcludingBouncerShowing = mKeyguardIsVisible
&& mDeviceInteractive && !mGoingToSleep
- && !statusBarShadeLocked && !mBouncerFullyShown;
+ && !statusBarShadeLocked && !mBouncerIsOrWillBeShowing;
final int user = getCurrentUser();
final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
final boolean isLockDown =
@@ -2563,6 +2563,7 @@
becauseCannotSkipBouncer,
biometricEnabledForUser,
mBouncerFullyShown,
+ mBouncerIsOrWillBeShowing,
faceAuthenticated,
faceDisabledForUser,
mGoingToSleep,
@@ -3131,7 +3132,7 @@
cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
}
}
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
}
if (wasBouncerFullyShown != mBouncerFullyShown) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a0ecd22..318529b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -42,7 +42,6 @@
import com.android.systemui.flags.FlagsModule;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.log.dagger.LogModule;
-import com.android.systemui.lowlightclock.LowLightClockController;
import com.android.systemui.media.dagger.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -94,7 +93,6 @@
import com.android.systemui.wallet.dagger.WalletModule;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.dagger.DynamicOverride;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -246,21 +244,6 @@
sysuiMainExecutor));
}
- @BindsOptionalOf
- @DynamicOverride
- abstract LowLightClockController optionalLowLightClockController();
-
- @SysUISingleton
- @Provides
- static Optional<LowLightClockController> provideLowLightClockController(
- @DynamicOverride Optional<LowLightClockController> optionalController) {
- if (optionalController.isPresent() && optionalController.get().isLowLightClockEnabled()) {
- return optionalController;
- } else {
- return Optional.empty();
- }
- }
-
@Binds
abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockController.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockController.java
deleted file mode 100644
index 0b15f4f..0000000
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightClockController.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.lowlightclock;
-
-import android.view.ViewGroup;
-
-/**
- * A controller responsible for attaching and showing an optional low-light clock while dozing.
- */
-public interface LowLightClockController {
- /**
- * Returns {@code true} if the low-light clock is enabled.
- */
- boolean isLowLightClockEnabled();
-
- /**
- * Attach the low light-clock to the given parent {@link ViewGroup}.
- * @param parent The parent {@link ViewGroup} to which the low-light clock view should be
- * attached.
- */
- void attachLowLightClockView(ViewGroup parent);
-
- /**
- * Show or hide the low-light clock.
- * @param show Whether to show the low-light clock.
- * @return {@code true} if the low-light clock was shown.
- */
- boolean showLowLightClock(boolean show);
-
- /**
- * An opportunity to perform burn-in prevention.
- */
- void dozeTimeTick();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 53abd99..0f1ee31 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -23,35 +23,47 @@
import android.os.Bundle
import android.os.IBinder
import android.os.ResultReceiver
-import android.view.View
+import android.os.UserHandle
+import android.widget.ImageView
import com.android.internal.app.ChooserActivity
+import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
import com.android.internal.app.chooser.TargetInfo
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.util.AsyncActivityLauncher
-import com.android.systemui.R;
-import javax.inject.Inject
+import com.android.systemui.R
+import com.android.internal.R as AndroidR
-class MediaProjectionAppSelectorActivity @Inject constructor(
- private val activityLauncher: AsyncActivityLauncher
+class MediaProjectionAppSelectorActivity constructor(
+ private val activityLauncher: AsyncActivityLauncher,
+ /** This is used to override the dependency in a screenshot test */
+ @VisibleForTesting
+ private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? = null
) : ChooserActivity() {
+ override fun getLayoutResource() =
+ R.layout.media_projection_app_selector
+
public override fun onCreate(bundle: Bundle?) {
val queryIntent = Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
- // TODO(b/235465652) Use resource lexeme
- intent.putExtra(Intent.EXTRA_TITLE, "Record or cast an app")
+ // TODO(b/240939253): update copies
+ val title = getString(R.string.media_projection_dialog_service_title)
+ intent.putExtra(Intent.EXTRA_TITLE, title)
super.onCreate(bundle)
- // TODO(b/235465652) we should update VisD of the title and add an icon
- findViewById<View>(R.id.title)?.visibility = View.VISIBLE
+ requireViewById<ImageView>(AndroidR.id.icon).setImageResource(R.drawable.ic_present_to_all)
}
override fun appliedThemeResId(): Int =
R.style.Theme_SystemUI_MediaProjectionAppSelector
+ override fun createListController(userHandle: UserHandle): ResolverListController =
+ listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
+
override fun startSelected(which: Int, always: Boolean, filtered: Boolean) {
val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter
val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
index e33a1b9..9696998 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
@@ -18,8 +18,10 @@
import android.app.Activity
import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.util.AsyncActivityLauncher
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@@ -29,7 +31,17 @@
@Binds
@IntoMap
@ClassKey(MediaProjectionAppSelectorActivity::class)
- abstract fun provideMediaProjectionAppSelectorActivity(
+ abstract fun bindMediaProjectionAppSelectorActivity(
activity: MediaProjectionAppSelectorActivity): Activity
+ companion object {
+ @Provides
+ fun provideMediaProjectionAppSelectorActivity(
+ activityLauncher: AsyncActivityLauncher
+ ): MediaProjectionAppSelectorActivity {
+ return MediaProjectionAppSelectorActivity(
+ activityLauncher
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index be6982a..5842665 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -165,7 +165,7 @@
iv.clearColorFilter();
}
if (state.state != mState) {
- int color = getColor(state.state);
+ int color = getColor(state);
mState = state.state;
if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
@@ -183,7 +183,7 @@
}
}
- protected int getColor(int state) {
+ protected int getColor(QSTile.State state) {
return getIconColorForState(getContext(), state);
}
@@ -239,19 +239,18 @@
/**
* Color to tint the tile icon based on state
*/
- public static int getIconColorForState(Context context, int state) {
- switch (state) {
- case Tile.STATE_UNAVAILABLE:
- return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA,
- Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary));
- case Tile.STATE_INACTIVE:
- return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
- case Tile.STATE_ACTIVE:
- return Utils.getColorAttrDefaultColor(context,
- com.android.internal.R.attr.textColorOnAccent);
- default:
- Log.e("QSIconView", "Invalid state " + state);
- return 0;
+ private static int getIconColorForState(Context context, QSTile.State state) {
+ if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
+ return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA,
+ Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary));
+ } else if (state.state == Tile.STATE_INACTIVE) {
+ return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
+ } else if (state.state == Tile.STATE_ACTIVE) {
+ return Utils.getColorAttrDefaultColor(context,
+ com.android.internal.R.attr.textColorOnAccent);
+ } else {
+ Log.e("QSIconView", "Invalid state " + state);
+ return 0;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 2731d64..163ee2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -35,6 +35,7 @@
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Switch
@@ -144,6 +145,7 @@
superSetVisibility = { super.setVisibility(it) },
superSetTransitionVisibility = { super.setTransitionVisibility(it) },
)
+ private var lastDisabledByPolicy = false
private val locInScreen = IntArray(2)
@@ -376,8 +378,22 @@
super.onInitializeAccessibilityNodeInfo(info)
// Clear selected state so it is not announce by talkback.
info.isSelected = false
+ if (lastDisabledByPolicy) {
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+ resources.getString(
+ R.string.accessibility_tile_disabled_by_policy_action_description
+ )
+ )
+ )
+ }
if (!TextUtils.isEmpty(accessibilityClass)) {
- info.className = accessibilityClass
+ info.className = if (lastDisabledByPolicy) {
+ Button::class.java.name
+ } else {
+ accessibilityClass
+ }
if (Switch::class.java.name == accessibilityClass) {
val label = resources.getString(
if (tileState) R.string.switch_bar_on else R.string.switch_bar_off)
@@ -430,6 +446,10 @@
state.secondaryLabel = stateText
}
}
+ if (state.disabledByPolicy && state.state != Tile.STATE_UNAVAILABLE) {
+ stateDescription.append(", ")
+ stateDescription.append(getUnavailableText(state.spec))
+ }
if (!TextUtils.isEmpty(state.stateDescription)) {
stateDescription.append(", ")
stateDescription.append(state.stateDescription)
@@ -470,38 +490,38 @@
}
// Colors
- if (state.state != lastState) {
+ if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) {
singleAnimator.cancel()
if (allowAnimations) {
singleAnimator.setValues(
colorValuesHolder(
BACKGROUND_NAME,
paintColor,
- getBackgroundColorForState(state.state)
+ getBackgroundColorForState(state.state, state.disabledByPolicy)
),
colorValuesHolder(
LABEL_NAME,
label.currentTextColor,
- getLabelColorForState(state.state)
+ getLabelColorForState(state.state, state.disabledByPolicy)
),
colorValuesHolder(
SECONDARY_LABEL_NAME,
secondaryLabel.currentTextColor,
- getSecondaryLabelColorForState(state.state)
+ getSecondaryLabelColorForState(state.state, state.disabledByPolicy)
),
colorValuesHolder(
CHEVRON_NAME,
chevronView.imageTintList?.defaultColor ?: 0,
- getChevronColorForState(state.state)
+ getChevronColorForState(state.state, state.disabledByPolicy)
)
)
singleAnimator.start()
} else {
setAllColors(
- getBackgroundColorForState(state.state),
- getLabelColorForState(state.state),
- getSecondaryLabelColorForState(state.state),
- getChevronColorForState(state.state)
+ getBackgroundColorForState(state.state, state.disabledByPolicy),
+ getLabelColorForState(state.state, state.disabledByPolicy),
+ getSecondaryLabelColorForState(state.state, state.disabledByPolicy),
+ getChevronColorForState(state.state, state.disabledByPolicy)
)
}
}
@@ -512,6 +532,7 @@
label.isEnabled = !state.disabledByPolicy
lastState = state.state
+ lastDisabledByPolicy = state.disabledByPolicy
}
private fun setAllColors(
@@ -559,13 +580,14 @@
}
}
- private fun getStateText(state: QSTile.State): String {
- if (state.disabledByPolicy) {
- return context.getString(R.string.tile_disabled)
- }
+ private fun getUnavailableText(spec: String?): String {
+ val arrayResId = SubtitleArrayMapping.getSubtitleId(spec)
+ return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE]
+ }
+ private fun getStateText(state: QSTile.State): String {
return if (state.state == Tile.STATE_UNAVAILABLE || state is BooleanState) {
- var arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec)
+ val arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec)
val array = resources.getStringArray(arrayResId)
array[state.state]
} else {
@@ -587,11 +609,11 @@
return locInScreen.get(1) >= -height
}
- private fun getBackgroundColorForState(state: Int): Int {
- return when (state) {
- Tile.STATE_ACTIVE -> colorActive
- Tile.STATE_INACTIVE -> colorInactive
- Tile.STATE_UNAVAILABLE -> colorUnavailable
+ private fun getBackgroundColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
+ return when {
+ state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorUnavailable
+ state == Tile.STATE_ACTIVE -> colorActive
+ state == Tile.STATE_INACTIVE -> colorInactive
else -> {
Log.e(TAG, "Invalid state $state")
0
@@ -599,11 +621,11 @@
}
}
- private fun getLabelColorForState(state: Int): Int {
- return when (state) {
- Tile.STATE_ACTIVE -> colorLabelActive
- Tile.STATE_INACTIVE -> colorLabelInactive
- Tile.STATE_UNAVAILABLE -> colorLabelUnavailable
+ private fun getLabelColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
+ return when {
+ state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorLabelUnavailable
+ state == Tile.STATE_ACTIVE -> colorLabelActive
+ state == Tile.STATE_INACTIVE -> colorLabelInactive
else -> {
Log.e(TAG, "Invalid state $state")
0
@@ -611,11 +633,11 @@
}
}
- private fun getSecondaryLabelColorForState(state: Int): Int {
- return when (state) {
- Tile.STATE_ACTIVE -> colorSecondaryLabelActive
- Tile.STATE_INACTIVE -> colorSecondaryLabelInactive
- Tile.STATE_UNAVAILABLE -> colorSecondaryLabelUnavailable
+ private fun getSecondaryLabelColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
+ return when {
+ state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorSecondaryLabelUnavailable
+ state == Tile.STATE_ACTIVE -> colorSecondaryLabelActive
+ state == Tile.STATE_INACTIVE -> colorSecondaryLabelInactive
else -> {
Log.e(TAG, "Invalid state $state")
0
@@ -623,7 +645,16 @@
}
}
- private fun getChevronColorForState(state: Int): Int = getSecondaryLabelColorForState(state)
+ private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int =
+ getSecondaryLabelColorForState(state, disabledByPolicy)
+
+ @VisibleForTesting
+ internal fun getCurrentColors(): List<Int> = listOf(
+ paintColor,
+ label.currentTextColor,
+ secondaryLabel.currentTextColor,
+ chevronView.imageTintList?.defaultColor ?: 0
+ )
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index fec76b6..e93f605 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -34,7 +34,6 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.lowlightclock.LowLightClockController;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -51,7 +50,6 @@
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import java.io.PrintWriter;
-import java.util.Optional;
import javax.inject.Inject;
@@ -88,7 +86,6 @@
private final DockManager mDockManager;
private final NotificationPanelViewController mNotificationPanelViewController;
private final PanelExpansionStateManager mPanelExpansionStateManager;
- private final Optional<LowLightClockController> mLowLightClockController;
private boolean mIsTrackingBarGesture = false;
@@ -106,7 +103,6 @@
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
StatusBarWindowStateController statusBarWindowStateController,
LockIconViewController lockIconViewController,
- Optional<LowLightClockController> lowLightClockController,
CentralSurfaces centralSurfaces,
NotificationShadeWindowController controller,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -125,7 +121,6 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mStatusBarWindowStateController = statusBarWindowStateController;
mLockIconViewController = lockIconViewController;
- mLowLightClockController = lowLightClockController;
mService = centralSurfaces;
mNotificationShadeWindowController = controller;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
@@ -148,8 +143,6 @@
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
mGestureDetector = new GestureDetector(mView.getContext(), mPulsingGestureListener);
- mLowLightClockController.ifPresent(controller -> controller.attachLowLightClockView(mView));
-
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
@@ -434,21 +427,6 @@
mStatusBarViewController = statusBarViewController;
}
- /**
- * Tell the controller that dozing has begun or ended.
- * @param dozing True if dozing has begun.
- */
- public void setDozing(boolean dozing) {
- mLowLightClockController.ifPresent(controller -> controller.showLowLightClock(dozing));
- }
-
- /**
- * Tell the controller to perform burn-in prevention.
- */
- public void dozeTimeTick() {
- mLowLightClockController.ifPresent(LowLightClockController::dozeTimeTick);
- }
-
@VisibleForTesting
void setDragDownHelper(DragDownHelper dragDownHelper) {
mDragDownHelper = dragDownHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index a57d849b2..25c6dce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -38,6 +38,7 @@
import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import java.util.ArrayList;
@@ -64,6 +65,15 @@
/**
* Designated constructor
+ *
+ * This view is special, in that it is the only view in SystemUI that allows for a configuration
+ * override on a MCC/MNC-basis. This means that for every mobile view inflated, we have to
+ * construct a context with that override, since the resource system doesn't have a way to
+ * handle this for us.
+ *
+ * @param context A context with resources configured by MCC/MNC
+ * @param slot The string key defining which slot this icon refers to. Always "mobile" for the
+ * mobile icon
*/
public static StatusBarMobileView fromContext(
Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
new file mode 100644
index 0000000..a02dd34
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.connectivity.ui
+
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Bundle
+import android.telephony.SubscriptionInfo
+import android.view.ContextThemeWrapper
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.connectivity.NetworkController
+import com.android.systemui.statusbar.connectivity.SignalCallback
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Every subscriptionId can have its own CarrierConfig associated with it, so we have to create our
+ * own [Configuration] and track resources based on the full set of available mcc-mnc combinations.
+ *
+ * (for future reference: b/240555502 is the initiating bug for this)
+ */
+@SysUISingleton
+class MobileContextProvider
+@Inject
+constructor(
+ networkController: NetworkController,
+ dumpManager: DumpManager,
+ private val demoModeController: DemoModeController,
+) : Dumpable, DemoMode {
+ private val subscriptions = mutableMapOf<Int, SubscriptionInfo>()
+ private val signalCallback =
+ object : SignalCallback {
+ override fun setSubs(subs: List<SubscriptionInfo>) {
+ subscriptions.clear()
+ subs.forEach { info -> subscriptions[info.subscriptionId] = info }
+ }
+ }
+
+ // These should always be null when not in demo mode
+ private var demoMcc: Int? = null
+ private var demoMnc: Int? = null
+
+ init {
+ networkController.addCallback(signalCallback)
+ dumpManager.registerDumpable(this)
+ demoModeController.addCallback(this)
+ }
+
+ /**
+ * @return a context with the MCC/MNC [Configuration] values corresponding to this
+ * subscriptionId
+ */
+ fun getMobileContextForSub(subId: Int, context: Context): Context {
+ if (demoModeController.isInDemoMode) {
+ return createMobileContextForDemoMode(context)
+ }
+
+ // Fail back to the given context if no sub exists
+ val info = subscriptions[subId] ?: return context
+
+ return createCarrierConfigContext(context, info.mcc, info.mnc)
+ }
+
+ /** For Demo mode (for now), just apply the same MCC/MNC override for all subIds */
+ private fun createMobileContextForDemoMode(context: Context): Context {
+ return createCarrierConfigContext(context, demoMcc ?: 0, demoMnc ?: 0)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(
+ "Subscriptions below will be inflated with a configuration context with " +
+ "MCC/MNC overrides"
+ )
+ subscriptions.forEach { (subId, info) ->
+ pw.println(" Subscription with subId($subId) with MCC/MNC(${info.mcc}/${info.mnc})")
+ }
+ pw.println(" MCC override: ${demoMcc ?: "(none)"}")
+ pw.println(" MNC override: ${demoMnc ?: "(none)"}")
+ }
+
+ override fun demoCommands(): List<String> {
+ return listOf(COMMAND_NETWORK)
+ }
+
+ override fun onDemoModeFinished() {
+ demoMcc = null
+ demoMnc = null
+ }
+
+ override fun dispatchDemoCommand(command: String, args: Bundle) {
+ val mccmnc = args.getString("mccmnc") ?: return
+ // Only length 5/6 strings are valid
+ if (!(mccmnc.length == 5 || mccmnc.length == 6)) {
+ return
+ }
+
+ // MCC is always the first 3 digits, and mnc is the last 2 or 3
+ demoMcc = mccmnc.subSequence(0, 3).toString().toInt()
+ demoMnc = mccmnc.subSequence(3, mccmnc.length).toString().toInt()
+ }
+
+ companion object {
+ /**
+ * Creates a context based on this [SubscriptionInfo]'s MCC/MNC values, allowing the overlay
+ * system to properly load different carrier's iconography
+ */
+ private fun createCarrierConfigContext(context: Context, mcc: Int, mnc: Int): Context {
+ // Copy the existing configuration
+ val c = Configuration(context.resources.configuration)
+ c.mcc = mcc
+ c.mnc = mnc
+
+ return ContextThemeWrapper(context, context.theme).also { ctx ->
+ ctx.applyOverrideConfiguration(c)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
index a10c745..0bcd3e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
@@ -26,10 +26,7 @@
class PipelineDumper(pw: PrintWriter) {
private val ipw = pw.asIndenting()
- fun print(a: Any?) = ipw.print(a)
fun println(a: Any?) = ipw.println(a)
- fun withIncreasedIndent(b: () -> Unit) = ipw.withIncreasedIndent(b)
- fun withIncreasedIndent(r: Runnable) = ipw.withIncreasedIndent(r)
fun dump(label: String, value: Any?) {
ipw.print("$label: ")
@@ -37,24 +34,26 @@
}
private fun dump(value: Any?) = when (value) {
- null, is String, is Int -> println(value)
+ null, is String, is Int -> ipw.println(value)
is Collection<*> -> dumpCollection(value)
else -> {
- println(value.fullPipelineName)
- withIncreasedIndent { (value as? PipelineDumpable)?.dumpPipeline(this) }
+ ipw.println(value.fullPipelineName)
+ (value as? PipelineDumpable)?.let {
+ ipw.withIncreasedIndent { it.dumpPipeline(this) }
+ }
}
}
private fun dumpCollection(values: Collection<Any?>) {
- println(values.size)
- withIncreasedIndent { values.forEach { dump(it) } }
+ ipw.println(values.size)
+ ipw.withIncreasedIndent { values.forEach { dump(it) } }
}
}
private val Any.bareClassName: String get() {
val className = javaClass.name
- val packageName = javaClass.`package`.name
- return className.substring(packageName.length + 1)
+ val packagePrefixLength = javaClass.`package`?.name?.length?.plus(1) ?: 0
+ return className.substring(packagePrefixLength)
}
private val Any.barePipelineName: String? get() = when (this) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
index 7a37846..3061522 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -78,10 +78,10 @@
}
override fun dumpPipeline(d: PipelineDumper) = with(d) {
- dump("ViewRenderer", viewRenderer)
- dump("OnAfterRenderListListeners", onAfterRenderListListeners)
- dump("OnAfterRenderGroupListeners", onAfterRenderGroupListeners)
- dump("OnAfterRenderEntryListeners", onAfterRenderEntryListeners)
+ dump("viewRenderer", viewRenderer)
+ dump("onAfterRenderListListeners", onAfterRenderListListeners)
+ dump("onAfterRenderGroupListeners", onAfterRenderGroupListeners)
+ dump("onAfterRenderEntryListeners", onAfterRenderEntryListeners)
}
private fun dispatchOnAfterRenderList(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index deb6150..1169d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -29,13 +31,13 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.R;
import com.android.systemui.demomode.DemoMode;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
@@ -49,7 +51,6 @@
private final LinearLayout mStatusIcons;
private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
private final int mIconSize;
- private final FeatureFlags mFeatureFlags;
private StatusBarWifiView mWifiView;
private boolean mDemoMode;
@@ -57,14 +58,12 @@
public DemoStatusIcons(
LinearLayout statusIcons,
- int iconSize,
- FeatureFlags featureFlags
+ int iconSize
) {
super(statusIcons.getContext());
mStatusIcons = statusIcons;
mIconSize = iconSize;
mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
- mFeatureFlags = featureFlags;
if (statusIcons instanceof StatusIconContainer) {
setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -253,9 +252,13 @@
}
}
- public void addMobileView(MobileIconState state) {
+ /**
+ * Add a new mobile icon view
+ */
+ public void addMobileView(MobileIconState state, Context mobileContext) {
Log.d(TAG, "addMobileView: ");
- StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, state.slot);
+ StatusBarMobileView view = StatusBarMobileView
+ .fromContext(mobileContext, state.slot);
view.applyMobileState(state);
view.setStaticDrawableColor(mColor);
@@ -265,19 +268,24 @@
addView(view, getChildCount(), createLayoutParams());
}
- public void updateMobileState(MobileIconState state) {
- Log.d(TAG, "updateMobileState: ");
- // If the view for this subId exists already, use it
+ /**
+ * Apply an update to a mobile icon view for the given {@link MobileIconState}. For
+ * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
+ * update it, since the context (and thus the {@link Configuration}) may have changed
+ */
+ public void updateMobileState(MobileIconState state, Context mobileContext) {
+ Log.d(TAG, "updateMobileState: " + state);
+
+ // The mobile config provided by MobileContextProvider could have changed; always recreate
for (int i = 0; i < mMobileViews.size(); i++) {
StatusBarMobileView view = mMobileViews.get(i);
if (view.getState().subId == state.subId) {
- view.applyMobileState(state);
- return;
+ removeView(view);
}
}
- // Else we have to add it
- addMobileView(state);
+ // Add the replacement or new icon
+ addMobileView(state, mobileContext);
}
public void onRemoveIcon(StatusIconDisplayable view) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 80c3e6c..ddff7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -226,7 +226,6 @@
}
mStatusBarStateController.setIsDozing(dozing);
- mNotificationShadeWindowViewController.setDozing(dozing);
if (mFoldAodAnimationController != null) {
mFoldAodAnimationController.setIsDozing(dozing);
}
@@ -310,7 +309,6 @@
public void dozeTimeTick() {
mNotificationPanel.dozeTimeTick();
mAuthController.dozeTimeTick();
- mNotificationShadeWindowViewController.dozeTimeTick();
if (mAmbientIndicationContainer instanceof DozeReceiver) {
((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 8273d57..bd99713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -36,7 +36,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.BaseStatusBarWifiView;
@@ -44,6 +43,7 @@
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
@@ -70,6 +70,7 @@
void addIconGroup(IconManager iconManager);
/** */
void removeIconGroup(IconManager iconManager);
+
/** Refresh the state of an IconManager by recreating the views */
void refreshIconGroup(IconManager iconManager);
/** */
@@ -82,21 +83,25 @@
void setSignalIcon(String slot, WifiIconState state);
/** */
void setMobileIcons(String slot, List<MobileIconState> states);
+
/**
* Display the no calling & SMS icons.
*/
void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
+
/**
* Display the no calling & SMS icons.
*/
void setNoCallingIcons(String slot, List<CallIndicatorIconState> states);
+
public void setIconVisibility(String slot, boolean b);
/**
* Sets the live region mode for the icon
- * @see android.view.View#setAccessibilityLiveRegion(int)
- * @param slot Icon slot to set region for
+ *
+ * @param slot Icon slot to set region for
* @param accessibilityLiveRegion live region mode for the icon
+ * @see android.view.View#setAccessibilityLiveRegion(int)
*/
void setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion);
@@ -115,8 +120,8 @@
static ArraySet<String> getIconHideList(Context context, String hideListStr) {
ArraySet<String> ret = new ArraySet<>();
String[] hideList = hideListStr == null
- ? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)
- : hideListStr.split(",");
+ ? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)
+ : hideListStr.split(",");
for (String slot : hideList) {
if (!TextUtils.isEmpty(slot)) {
ret.add(slot);
@@ -134,11 +139,14 @@
public DarkIconManager(
LinearLayout linearLayout,
- FeatureFlags featureFlags,
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
+ MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
- super(linearLayout, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
+ super(linearLayout,
+ statusBarPipelineFlags,
+ wifiViewModelProvider,
+ mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
mDarkIconDispatcher = darkIconDispatcher;
@@ -195,41 +203,49 @@
@SysUISingleton
public static class Factory {
- private final FeatureFlags mFeatureFlags;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final MobileContextProvider mMobileContextProvider;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
public Factory(
- FeatureFlags featureFlags,
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
+ MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
- mFeatureFlags = featureFlags;
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModelProvider = wifiViewModelProvider;
+ mMobileContextProvider = mobileContextProvider;
mDarkIconDispatcher = darkIconDispatcher;
}
public DarkIconManager create(LinearLayout group) {
return new DarkIconManager(
- group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider,
+ group,
+ mStatusBarPipelineFlags,
+ mWifiViewModelProvider,
+ mMobileContextProvider,
mDarkIconDispatcher);
}
}
}
- /** */
+ /**
+ *
+ */
class TintedIconManager extends IconManager {
private int mColor;
public TintedIconManager(
ViewGroup group,
- FeatureFlags featureFlags,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider) {
- super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
+ Provider<WifiViewModel> wifiViewModelProvider,
+ MobileContextProvider mobileContextProvider) {
+ super(group,
+ statusBarPipelineFlags,
+ wifiViewModelProvider,
+ mobileContextProvider);
}
@Override
@@ -261,23 +277,26 @@
@SysUISingleton
public static class Factory {
- private final FeatureFlags mFeatureFlags;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final MobileContextProvider mMobileContextProvider;
@Inject
public Factory(
- FeatureFlags featureFlags,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider) {
- mFeatureFlags = featureFlags;
+ Provider<WifiViewModel> wifiViewModelProvider,
+ MobileContextProvider mobileContextProvider) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModelProvider = wifiViewModelProvider;
+ mMobileContextProvider = mobileContextProvider;
}
public TintedIconManager create(ViewGroup group) {
return new TintedIconManager(
- group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider);
+ group,
+ mStatusBarPipelineFlags,
+ mWifiViewModelProvider,
+ mMobileContextProvider);
}
}
}
@@ -287,9 +306,9 @@
*/
class IconManager implements DemoModeCommandReceiver {
protected final ViewGroup mGroup;
- private final FeatureFlags mFeatureFlags;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final MobileContextProvider mMobileContextProvider;
protected final Context mContext;
protected final int mIconSize;
// Whether or not these icons show up in dumpsys
@@ -305,13 +324,13 @@
public IconManager(
ViewGroup group,
- FeatureFlags featureFlags,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider) {
+ Provider<WifiViewModel> wifiViewModelProvider,
+ MobileContextProvider mobileContextProvider) {
mGroup = group;
- mFeatureFlags = featureFlags;
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModelProvider = wifiViewModelProvider;
+ mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
@@ -403,12 +422,15 @@
@VisibleForTesting
protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
- StatusBarMobileView view = onCreateStatusBarMobileView(slot);
+ // Use the `subId` field as a key to query for the correct context
+ StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
view.applyMobileState(state);
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
- mDemoStatusIcons.addMobileView(state);
+ Context mobileContext = mMobileContextProvider
+ .getMobileContextForSub(state.subId, mContext);
+ mDemoStatusIcons.addMobileView(state, mobileContext);
}
return view;
}
@@ -427,8 +449,10 @@
mContext, slot, mWifiViewModelProvider.get());
}
- private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
- StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot);
+ private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
+ Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
+ StatusBarMobileView view = StatusBarMobileView
+ .fromContext(mobileContext, slot);
return view;
}
@@ -516,7 +540,9 @@
}
if (mIsInDemoMode) {
- mDemoStatusIcons.updateMobileState(state);
+ Context mobileContext = mMobileContextProvider
+ .getMobileContextForSub(state.subId, mContext);
+ mDemoStatusIcons.updateMobileState(state, mobileContext);
}
}
@@ -553,7 +579,7 @@
}
protected DemoStatusIcons createDemoStatusIcons() {
- return new DemoStatusIcons((LinearLayout) mGroup, mIconSize, mFeatureFlags);
+ return new DemoStatusIcons((LinearLayout) mGroup, mIconSize);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index e910f72..619e50b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -58,7 +59,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSIconViewImpl;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter;
@@ -304,7 +304,7 @@
MemoryIconDrawable(Context context) {
baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
dp = context.getResources().getDisplayMetrics().density;
- paint.setColor(QSIconViewImpl.getIconColorForState(context, STATE_ACTIVE));
+ paint.setColor(Color.WHITE);
}
public void setRss(long rss) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index bb455da..0bf038d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -86,6 +86,7 @@
becauseCannotSkipBouncer = false,
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
+ bouncerIsOrWillShow = false,
onlyFaceEnrolled = false,
faceAuthenticated = false,
faceDisabled = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c281965..f5b9503 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1511,7 +1511,7 @@
anyBoolean());
CancellationSignal cancelSignal = mCancellationSignalCaptor.getValue();
- bouncerFullyVisible();
+ bouncerWillBeVisibleSoon();
mTestableLooper.processAllMessages();
assertThat(cancelSignal.isCanceled()).isTrue();
@@ -1669,6 +1669,11 @@
setKeyguardBouncerVisibility(true);
}
+ private void bouncerWillBeVisibleSoon() {
+ mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true, false);
+ mTestableLooper.processAllMessages();
+ }
+
private void setKeyguardBouncerVisibility(boolean isVisible) {
mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 23e5168..2c76be6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -97,7 +97,7 @@
ImageView iv = mock(ImageView.class);
State s = new State();
s.state = Tile.STATE_ACTIVE;
- int desiredColor = mIconView.getColor(s.state);
+ int desiredColor = mIconView.getColor(s);
when(iv.isShown()).thenReturn(true);
mIconView.setIcon(iv, s, true);
@@ -109,7 +109,7 @@
ImageView iv = mock(ImageView.class);
State s = new State();
s.state = Tile.STATE_ACTIVE;
- int desiredColor = mIconView.getColor(s.state);
+ int desiredColor = mIconView.getColor(s);
Icon i = mock(Icon.class);
s.icon = i;
when(i.toString()).thenReturn("MOCK ICON");
@@ -124,6 +124,18 @@
assertFalse(mIconView.toString().contains("lastIcon"));
}
+ @Test
+ public void testIconColorDisabledByPolicy_sameAsUnavailable() {
+ State s1 = new State();
+ s1.state = Tile.STATE_INACTIVE;
+ s1.disabledByPolicy = true;
+
+ State s2 = new State();
+ s2.state = Tile.STATE_UNAVAILABLE;
+
+ assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
+ }
+
private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
return new Drawable.ConstantState() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 9fdc2fd..d3ec1dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -280,6 +280,88 @@
assertThat(info.collectionItemInfo).isNull()
}
+ @Test
+ fun testDisabledByPolicyInactive_usesUnavailableColors() {
+ val stateDisabledByPolicy = QSTile.State()
+ stateDisabledByPolicy.state = Tile.STATE_INACTIVE
+ stateDisabledByPolicy.disabledByPolicy = true
+
+ val stateUnavailable = QSTile.State()
+ stateUnavailable.state = Tile.STATE_UNAVAILABLE
+
+ tileView.changeState(stateDisabledByPolicy)
+ val colorsDisabledByPolicy = tileView.getCurrentColors()
+
+ tileView.changeState(stateUnavailable)
+ val colorsUnavailable = tileView.getCurrentColors()
+
+ assertThat(colorsDisabledByPolicy).containsExactlyElementsIn(colorsUnavailable)
+ }
+
+ @Test
+ fun testDisabledByPolicyActive_usesUnavailableColors() {
+ val stateDisabledByPolicy = QSTile.State()
+ stateDisabledByPolicy.state = Tile.STATE_ACTIVE
+ stateDisabledByPolicy.disabledByPolicy = true
+
+ val stateUnavailable = QSTile.State()
+ stateUnavailable.state = Tile.STATE_UNAVAILABLE
+
+ tileView.changeState(stateDisabledByPolicy)
+ val colorsDisabledByPolicy = tileView.getCurrentColors()
+
+ tileView.changeState(stateUnavailable)
+ val colorsUnavailable = tileView.getCurrentColors()
+
+ assertThat(colorsDisabledByPolicy).containsExactlyElementsIn(colorsUnavailable)
+ }
+
+ @Test
+ fun testDisabledByPolicy_secondaryLabelText() {
+ val testA11yLabel = "TEST_LABEL"
+ context.orCreateTestableResources
+ .addOverride(
+ R.string.accessibility_tile_disabled_by_policy_action_description,
+ testA11yLabel
+ )
+
+ val stateDisabledByPolicy = QSTile.State()
+ stateDisabledByPolicy.state = Tile.STATE_INACTIVE
+ stateDisabledByPolicy.disabledByPolicy = true
+
+ tileView.changeState(stateDisabledByPolicy)
+
+ val info = AccessibilityNodeInfo(tileView)
+ tileView.onInitializeAccessibilityNodeInfo(info)
+ assertThat(
+ info.actionList.find {
+ it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id
+ }?.label
+ ).isEqualTo(testA11yLabel)
+ }
+
+ @Test
+ fun testDisabledByPolicy_unavailableInStateDescription() {
+ val state = QSTile.BooleanState()
+ val spec = "internet"
+ state.spec = spec
+ state.disabledByPolicy = true
+ state.state = Tile.STATE_INACTIVE
+
+ val unavailableString = "${spec}_unavailable"
+ val offString = "${spec}_off"
+ val onString = "${spec}_on"
+
+ context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf(
+ unavailableString,
+ offString,
+ onString
+ ))
+
+ tileView.changeState(state)
+ assertThat(tileView.stateDescription?.contains(unavailableString)).isTrue()
+ }
+
class FakeTileView(
context: Context,
icon: QSIconView,
@@ -289,4 +371,4 @@
handleStateChanged(state)
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 6d059b1..43fc8983 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -25,7 +25,6 @@
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.lowlightclock.LowLightClockController
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -39,12 +38,10 @@
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.never
@@ -87,8 +84,6 @@
@Mock
private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock
- private lateinit var lowLightClockController: LowLightClockController
- @Mock
private lateinit var pulsingGestureListener: PulsingGestureListener
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -114,7 +109,6 @@
statusBarKeyguardViewManager,
statusBarWindowStateController,
lockIconViewController,
- Optional.of(lowLightClockController),
centralSurfaces,
notificationShadeWindowController,
keyguardUnlockAnimationController,
@@ -252,31 +246,6 @@
verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
assertThat(returnVal).isTrue()
}
-
- @Test
- fun testLowLightClockAttachedWhenExpandedStatusBarSetup() {
- verify(lowLightClockController).attachLowLightClockView(ArgumentMatchers.any())
- }
-
- @Test
- fun testLowLightClockShownWhenDozing() {
- underTest.setDozing(true)
- verify(lowLightClockController).showLowLightClock(true)
- }
-
- @Test
- fun testLowLightClockDozeTimeTickCalled() {
- underTest.dozeTimeTick()
- verify(lowLightClockController).dozeTimeTick()
- }
-
- @Test
- fun testLowLightClockHiddenWhenNotDozing() {
- underTest.setDozing(true)
- verify(lowLightClockController).showLowLightClock(true)
- underTest.setDozing(false)
- verify(lowLightClockController).showLowLightClock(false)
- }
}
private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 89a2518..001bfee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -38,7 +38,6 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.lowlightclock.LowLightClockController;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -61,8 +60,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -86,7 +83,6 @@
@Mock private StatusBarWindowStateController mStatusBarWindowStateController;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock private LockIconViewController mLockIconViewController;
- @Mock private LowLightClockController mLowLightClockController;
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock private AmbientState mAmbientState;
@Mock private PulsingGestureListener mPulsingGestureListener;
@@ -121,7 +117,6 @@
mStatusBarKeyguardViewManager,
mStatusBarWindowStateController,
mLockIconViewController,
- Optional.of(mLowLightClockController),
mCentralSurfaces,
mNotificationShadeWindowController,
mKeyguardUnlockAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
new file mode 100644
index 0000000..0fdda62
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.connectivity.ui
+
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.connectivity.NetworkController
+import com.android.systemui.statusbar.connectivity.SignalCallback
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileContextProviderTest : SysuiTestCase() {
+ @Mock private lateinit var networkController: NetworkController
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var demoModeController: DemoModeController
+
+ private lateinit var provider: MobileContextProvider
+ private lateinit var signalCallback: SignalCallback
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ provider =
+ MobileContextProvider(
+ networkController,
+ dumpManager,
+ demoModeController,
+ )
+
+ signalCallback = withArgCaptor { verify(networkController).addCallback(capture()) }
+ }
+
+ @Test
+ fun test_oneSubscription_contextHasMccMnc() {
+ // GIVEN there is one SubscriptionInfo
+ signalCallback.setSubs(listOf(SUB_1))
+
+ // WHEN we ask for a mobile context
+ val ctx = provider.getMobileContextForSub(SUB_1_ID, context)
+
+ // THEN the configuration of that context reflect this subscription's MCC/MNC override
+ val config = ctx.resources.configuration
+ assertThat(config.mcc).isEqualTo(SUB_1_MCC)
+ assertThat(config.mnc).isEqualTo(SUB_1_MNC)
+ }
+
+ @Test
+ fun test_twoSubscriptions_eachContextReflectsMccMnc() {
+ // GIVEN there are two SubscriptionInfos
+ signalCallback.setSubs(listOf(SUB_1, SUB_2))
+
+ // WHEN we ask for a mobile context for each sub
+ val ctx1 = provider.getMobileContextForSub(SUB_1_ID, context)
+ val ctx2 = provider.getMobileContextForSub(SUB_2_ID, context)
+
+ // THEN the configuration of each context reflect this subscription's MCC/MNC override
+ val config1 = ctx1.resources.configuration
+ assertThat(config1.mcc).isEqualTo(SUB_1_MCC)
+ assertThat(config1.mnc).isEqualTo(SUB_1_MNC)
+
+ val config2 = ctx2.resources.configuration
+ assertThat(config2.mcc).isEqualTo(SUB_2_MCC)
+ assertThat(config2.mnc).isEqualTo(SUB_2_MNC)
+ }
+
+ @Test
+ fun test_requestingContextForNonexistentSubscription_returnsGivenContext() {
+ // GIVEN no SubscriptionInfos
+ signalCallback.setSubs(listOf())
+
+ // WHEN we ask for a mobile context for an unknown subscription
+ val ctx = provider.getMobileContextForSub(SUB_1_ID, context)
+
+ // THEN we get the original context back
+ assertThat(ctx).isEqualTo(context)
+ }
+
+ private val SUB_1_ID = 1
+ private val SUB_1_MCC = 123
+ private val SUB_1_MNC = 456
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_1_ID)
+ whenever(it.mcc).thenReturn(SUB_1_MCC)
+ whenever(it.mnc).thenReturn(SUB_1_MNC)
+ }
+
+ private val SUB_2_ID = 2
+ private val SUB_2_MCC = 666
+ private val SUB_2_MNC = 777
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_2_ID)
+ whenever(it.mcc).thenReturn(SUB_2_MCC)
+ whenever(it.mnc).thenReturn(SUB_2_MNC)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index ca98143..de7db74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -20,7 +20,10 @@
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -30,12 +33,12 @@
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
@@ -55,15 +58,19 @@
@SmallTest
public class StatusBarIconControllerTest extends LeakCheckedTest {
+ private MobileContextProvider mMobileContextProvider = mock(MobileContextProvider.class);
+
@Before
public void setup() {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+ // For testing, ignore context overrides
+ when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext);
}
@Test
public void testSetCalledOnAdd_IconManager() {
LinearLayout layout = new LinearLayout(mContext);
- TestIconManager manager = new TestIconManager(layout);
+ TestIconManager manager = new TestIconManager(layout, mMobileContextProvider);
testCallOnAdd_forManager(manager);
}
@@ -72,9 +79,9 @@
LinearLayout layout = new LinearLayout(mContext);
TestDarkIconManager manager = new TestDarkIconManager(
layout,
- mock(FeatureFlags.class),
mock(StatusBarPipelineFlags.class),
() -> mock(WifiViewModel.class),
+ mMobileContextProvider,
mock(DarkIconDispatcher.class));
testCallOnAdd_forManager(manager);
}
@@ -114,11 +121,14 @@
TestDarkIconManager(
LinearLayout group,
- FeatureFlags featureFlags,
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
+ MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
- super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider,
+ super(group,
+ statusBarPipelineFlags,
+ wifiViewModelProvider,
+ contextProvider,
darkIconDispatcher);
}
@@ -153,11 +163,11 @@
}
private static class TestIconManager extends IconManager implements TestableIconManager {
- TestIconManager(ViewGroup group) {
+ TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
super(group,
- mock(FeatureFlags.class),
mock(StatusBarPipelineFlags.class),
- () -> mock(WifiViewModel.class));
+ () -> mock(WifiViewModel.class),
+ contextProvider);
}
@Override
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 07b6843..7cb2fd0 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -28,6 +28,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.IVpnManager;
@@ -771,6 +772,12 @@
@VisibleForTesting
void onUserStarted(int userId) {
+ UserInfo user = mUserManager.getUserInfo(userId);
+ if (user == null) {
+ logw("Started user doesn't exist. UserId: " + userId);
+ return;
+ }
+
synchronized (mVpns) {
Vpn userVpn = mVpns.get(userId);
if (userVpn != null) {
@@ -779,7 +786,8 @@
}
userVpn = mDeps.createVpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId);
mVpns.put(userId, userVpn);
- if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+
+ if (user.isPrimary() && isLockdownVpnEnabled()) {
updateLockdownVpn();
}
}
@@ -896,9 +904,15 @@
}
private void onUserUnlocked(int userId) {
+ UserInfo user = mUserManager.getUserInfo(userId);
+ if (user == null) {
+ logw("Unlocked user doesn't exist. UserId: " + userId);
+ return;
+ }
+
synchronized (mVpns) {
// User present may be sent because of an unlock, which might mean an unlocked keystore.
- if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+ if (user.isPrimary() && isLockdownVpnEnabled()) {
updateLockdownVpn();
} else {
startAlwaysOnVpn(userId);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 98238cc..b988b57 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -747,7 +747,7 @@
return true;
}
- private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+ private Intent buildVpnManagerEventIntent(@NonNull String category, int errorClass,
int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
@NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
@Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
@@ -766,6 +766,20 @@
intent.putExtra(VpnManager.EXTRA_ERROR_CODE, errorCode);
}
+ return intent;
+ }
+
+ private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+ int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
+ @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
+ @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+ final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
+ packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
+ return sendEventToVpnManagerApp(intent, packageName);
+ }
+
+ private boolean sendEventToVpnManagerApp(@NonNull final Intent intent,
+ @NonNull final String packageName) {
// Allow VpnManager app to temporarily run background services to handle this error.
// If an app requires anything beyond this grace period, they MUST either declare
// themselves as a foreground service, or schedule a job/workitem.
@@ -1177,23 +1191,25 @@
mContext.unbindService(mConnection);
cleanupVpnStateLocked();
} else if (mVpnRunner != null) {
- if (!VpnConfig.LEGACY_VPN.equals(mPackage)) {
- mAppOpsManager.finishOp(
- AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, mOwnerUID, mPackage, null);
- // The underlying network, NetworkCapabilities and LinkProperties are not
- // necessary to send to VPN app since the purpose of this event is to notify
- // VPN app that VPN is deactivated by the user.
- // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
- // ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
- -1 /* errorClass */, -1 /* errorCode*/, mPackage,
- getSessionKeyLocked(), makeVpnProfileStateLocked(),
- null /* underlyingNetwork */, null /* nc */, null /* lp */);
- }
+ // Build intent first because the sessionKey will be reset after performing
+ // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+ // VpnRunner.exit() to prevent design being changed in the future.
+ // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+ // ConnectivityServiceTest.
+ final int ownerUid = mOwnerUID;
+ Intent intent = null;
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
+ intent = buildVpnManagerEventIntent(
+ VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode*/, mPackage,
+ getSessionKeyLocked(), makeVpnProfileStateLocked(),
+ null /* underlyingNetwork */, null /* nc */, null /* lp */);
}
// cleanupVpnStateLocked() is called from mVpnRunner.exit()
mVpnRunner.exit();
+ if (intent != null && isVpnApp(mPackage)) {
+ notifyVpnManagerVpnStopped(mPackage, ownerUid, intent);
+ }
}
try {
@@ -2887,6 +2903,9 @@
final LinkProperties lp;
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
mInterface = interfaceName;
mConfig.mtu = maxMtu;
mConfig.interfaze = mInterface;
@@ -2988,6 +3007,9 @@
try {
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
mConfig.underlyingNetworks = new Network[] {network};
mNetworkCapabilities =
new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3077,7 +3099,12 @@
// Clear mInterface to prevent Ikev2VpnRunner being cleared when
// interfaceRemoved() is called.
- mInterface = null;
+ synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
+ mInterface = null;
+ }
// Without MOBIKE, we have no way to seamlessly migrate. Close on old
// (non-default) network, and start the new one.
resetIkeState();
@@ -3262,6 +3289,9 @@
/** Marks the state as FAILED, and disconnects. */
private void markFailedAndDisconnect(Exception exception) {
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
updateState(DetailedState.FAILED, exception.getMessage());
}
@@ -3300,6 +3330,9 @@
cancelHandleNetworkLostTimeout();
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
if (exception instanceof IkeProtocolException) {
final IkeProtocolException ikeException = (IkeProtocolException) exception;
@@ -3420,6 +3453,9 @@
Log.d(TAG, "Resetting state for token: " + mCurrentToken);
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
// Since this method handles non-fatal errors only, set mInterface to null to
// prevent the NetworkManagementEventObserver from killing this VPN based on the
// interface going down (which we expect).
@@ -4090,7 +4126,32 @@
// To stop the VPN profile, the caller must be the current prepared package and must be
// running an Ikev2VpnProfile.
if (isCurrentIkev2VpnLocked(packageName)) {
- prepareInternal(VpnConfig.LEGACY_VPN);
+ // Build intent first because the sessionKey will be reset after performing
+ // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+ // VpnRunner.exit() to prevent design being changed in the future.
+ final int ownerUid = mOwnerUID;
+ final Intent intent = buildVpnManagerEventIntent(
+ VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode*/, packageName,
+ getSessionKeyLocked(), makeVpnProfileStateLocked(),
+ null /* underlyingNetwork */, null /* nc */, null /* lp */);
+
+ mVpnRunner.exit();
+ notifyVpnManagerVpnStopped(packageName, ownerUid, intent);
+ }
+ }
+
+ private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID,
+ Intent intent) {
+ mAppOpsManager.finishOp(
+ AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, ownerUID, packageName, null);
+ // The underlying network, NetworkCapabilities and LinkProperties are not
+ // necessary to send to VPN app since the purpose of this event is to notify
+ // VPN app that VPN is deactivated by the user.
+ // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+ // ConnectivityServiceTest.
+ if (SdkLevel.isAtLeastT()) {
+ sendEventToVpnManagerApp(intent, packageName);
}
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index c835d2f..25d0752 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -116,10 +116,8 @@
luxLevels = getLuxLevels(resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevelsIdle));
} else {
- brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
- luxLevels = getLuxLevels(resources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLevels));
+ brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
+ luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
}
// Display independent, mode independent values
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4f3fd64..12b2f47 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
import android.os.Environment;
@@ -149,12 +150,22 @@
* </quirks>
*
* <autoBrightness>
- * <brighteningLightDebounceMillis>
+ * <brighteningLightDebounceMillis>
* 2000
- * </brighteningLightDebounceMillis>
+ * </brighteningLightDebounceMillis>
* <darkeningLightDebounceMillis>
* 1000
* </darkeningLightDebounceMillis>
+ * <displayBrightnessMapping>
+ * <displayBrightnessPoint>
+ * <lux>50</lux>
+ * <nits>45.32</nits>
+ * </displayBrightnessPoint>
+ * <displayBrightnessPoint>
+ * <lux>80</lux>
+ * <nits>75.43</nits>
+ * </displayBrightnessPoint>
+ * </displayBrightnessMapping>
* </autoBrightness>
*
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -268,6 +279,34 @@
// for the corresponding values above
private float[] mBrightness;
+
+ /**
+ * Array of desired screen brightness in nits corresponding to the lux values
+ * in the mBrightnessLevelsLux array. The display brightness is defined as the
+ * measured brightness of an all-white image. The brightness values must be non-negative and
+ * non-decreasing. This must be overridden in platform specific overlays
+ */
+ private float[] mBrightnessLevelsNits;
+
+ /**
+ * Array of light sensor lux values to define our levels for auto backlight
+ * brightness support.
+
+ * The N + 1 entries of this array define N control points defined in mBrightnessLevelsNits,
+ * with first value always being 0 lux
+
+ * The control points must be strictly increasing. Each control point
+ * corresponds to an entry in the brightness backlight values arrays.
+ * For example, if lux == level[1] (second element of the levels array)
+ * then the brightness will be determined by value[0] (first element
+ * of the brightness values array).
+ *
+ * Spline interpolation is used to determine the auto-brightness
+ * backlight values for lux levels between these control points.
+ *
+ */
+ private float[] mBrightnessLevelsLux;
+
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
private float mBrightnessDefault = Float.NaN;
@@ -661,6 +700,20 @@
return mAutoBrightnessBrighteningLightDebounce;
}
+ /**
+ * @return Auto brightness brightening ambient lux levels
+ */
+ public float[] getAutoBrightnessBrighteningLevelsLux() {
+ return mBrightnessLevelsLux;
+ }
+
+ /**
+ * @return Auto brightness brightening nits levels
+ */
+ public float[] getAutoBrightnessBrighteningLevelsNits() {
+ return mBrightnessLevelsNits;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -703,6 +756,8 @@
+ mAutoBrightnessBrighteningLightDebounce
+ ", mAutoBrightnessDarkeningLightDebounce= "
+ mAutoBrightnessDarkeningLightDebounce
+ + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
+ + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ "}";
}
@@ -779,6 +834,7 @@
loadBrightnessRampsFromConfigXml();
loadAmbientLightSensorFromConfigXml();
setProxSensorUnspecified();
+ loadAutoBrightnessConfigsFromConfigXml();
mLoadedFrom = "<config.xml>";
}
@@ -991,6 +1047,7 @@
private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
+ loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
}
/**
@@ -1023,6 +1080,35 @@
}
}
+ /**
+ * Loads the auto-brightness display brightness mappings. Internally, this takes care of
+ * loading the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
+ if (autoBrightnessConfig == null
+ || autoBrightnessConfig.getDisplayBrightnessMapping() == null) {
+ mBrightnessLevelsNits = getFloatArray(mContext.getResources()
+ .obtainTypedArray(com.android.internal.R.array
+ .config_autoBrightnessDisplayValuesNits), PowerManager
+ .BRIGHTNESS_OFF_FLOAT);
+ mBrightnessLevelsLux = getLuxLevels(mContext.getResources()
+ .getIntArray(com.android.internal.R.array
+ .config_autoBrightnessLevels));
+ } else {
+ final int size = autoBrightnessConfig.getDisplayBrightnessMapping()
+ .getDisplayBrightnessPoint().size();
+ mBrightnessLevelsNits = new float[size];
+ // The first control point is implicit and always at 0 lux.
+ mBrightnessLevelsLux = new float[size + 1];
+ for (int i = 0; i < size; i++) {
+ mBrightnessLevelsNits[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
+ .getDisplayBrightnessPoint().get(i).getNits().floatValue();
+ mBrightnessLevelsLux[i + 1] = autoBrightnessConfig.getDisplayBrightnessMapping()
+ .getDisplayBrightnessPoint().get(i).getLux().floatValue();
+ }
+ }
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -1248,6 +1334,10 @@
com.android.internal.R.string.config_displayLightSensorType);
}
+ private void loadAutoBrightnessConfigsFromConfigXml() {
+ loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
+ }
+
private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
final SensorDetails sensorDetails = config.getLightSensor();
if (sensorDetails != null) {
@@ -1390,6 +1480,31 @@
}
}
+ /**
+ * Extracts a float array from the specified {@link TypedArray}.
+ *
+ * @param array The array to convert.
+ * @return the given array as a float array.
+ */
+ public static float[] getFloatArray(TypedArray array, float defaultValue) {
+ final int n = array.length();
+ float[] vals = new float[n];
+ for (int i = 0; i < n; i++) {
+ vals[i] = array.getFloat(i, defaultValue);
+ }
+ array.recycle();
+ return vals;
+ }
+
+ private static float[] getLuxLevels(int[] lux) {
+ // The first control point is implicit and always at 0 lux.
+ float[] levels = new float[lux.length + 1];
+ for (int i = 0; i < lux.length; i++) {
+ levels[i + 1] = (float) lux[i];
+ }
+ return levels;
+ }
+
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d44a3b7..4e0cc61 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4953,7 +4953,16 @@
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
- return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule,
+ // If the caller is system, take the package name from the rule's owner rather than
+ // from the caller's package.
+ String rulePkg = pkg;
+ if (isCallingUidSystem()) {
+ if (automaticZenRule.getOwner() != null) {
+ rulePkg = automaticZenRule.getOwner().getPackageName();
+ }
+ }
+
+ return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
"addAutomaticZenRule");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0c4273f..e9d5426 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5798,6 +5798,11 @@
final Computer snapshot = snapshotComputer();
enforceOwnerRights(snapshot, packageName, Binder.getCallingUid());
mimeTypes = CollectionUtils.emptyIfNull(mimeTypes);
+ for (String mimeType : mimeTypes) {
+ if (mimeType.length() > 255) {
+ throw new IllegalArgumentException("MIME type length exceeds 255 characters");
+ }
+ }
final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
Set<String> existingMimeTypes = packageState.getMimeGroups().get(mimeGroup);
if (existingMimeTypes == null) {
@@ -5808,6 +5813,10 @@
&& existingMimeTypes.containsAll(mimeTypes)) {
return;
}
+ if (mimeTypes.size() > 500) {
+ throw new IllegalStateException("Max limit on MIME types for MIME group "
+ + mimeGroup + " exceeded for package " + packageName);
+ }
ArraySet<String> mimeTypesSet = new ArraySet<>(mimeTypes);
commitPackageStateMutation(null, packageName, packageStateWrite -> {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b3ba20b..37538db 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1112,15 +1112,21 @@
resolvedAttributionSource, skipCurrentFinish);
}
- if (next == null || next.getNext() == null) {
- return;
- }
-
RegisteredAttribution registered =
sRunningAttributionSources.remove(current.getToken());
if (registered != null) {
registered.unregister();
}
+
+ if (next == null || next.getNext() == null) {
+ if (next != null) {
+ registered = sRunningAttributionSources.remove(next.getToken());
+ if (registered != null) {
+ registered.unregister();
+ }
+ }
+ return;
+ }
current = next;
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 67d9aec..6c8e9f0 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -1856,6 +1856,9 @@
for (int i = component.getIntents().size() - 1; i >= 0; i--) {
IntentFilter filter = component.getIntents().get(i).getIntentFilter();
for (int groupIndex = filter.countMimeGroups() - 1; groupIndex >= 0; groupIndex--) {
+ if (mimeGroups != null && mimeGroups.size() > 500) {
+ throw new IllegalStateException("Max limit on number of MIME Groups reached");
+ }
mimeGroups = ArrayUtils.add(mimeGroups, filter.getMimeGroup(groupIndex));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d572b1b..af22f80 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -357,7 +357,6 @@
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.utils.InsetUtils;
-import com.android.server.wm.utils.WmDisplayCutout;
import dalvik.annotation.optimization.NeverCompile;
@@ -918,7 +917,6 @@
*/
private final Configuration mTmpConfig = new Configuration();
private final Rect mTmpBounds = new Rect();
- private final Rect mTmpOutNonDecorBounds = new Rect();
// Token for targeting this activity for assist purposes.
final Binder assistToken = new Binder();
@@ -8152,10 +8150,12 @@
final int orientation = parentBounds.height() >= parentBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// Compute orientation from stable parent bounds (= parent bounds with insets applied)
+ final DisplayInfo di = isFixedRotationTransforming()
+ ? getFixedRotationTransformDisplayInfo()
+ : mDisplayContent.getDisplayInfo();
final Task task = getTask();
- task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */,
- outStableBounds /* outStableBounds */, parentBounds /* bounds */,
- mDisplayContent.getDisplayInfo());
+ task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
@@ -9718,10 +9718,10 @@
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth;
final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
- final WmDisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation);
- policy.getNonDecorInsetsLw(rotation, dw, dh, cutout, mNonDecorInsets[rotation]);
- mStableInsets[rotation].set(mNonDecorInsets[rotation]);
- policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation);
+ final DisplayPolicy.DecorInsets.Info decorInfo =
+ policy.getDecorInsetsInfo(rotation, dw, dh);
+ mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
+ mStableInsets[rotation].set(decorInfo.mConfigInsets);
if (unfilledContainerBounds == null) {
continue;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 603a792..720b082 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1080,7 +1080,7 @@
* mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
mInsetsStateController = new InsetsStateController(this);
- mDisplayFrames = new DisplayFrames(mDisplayId, mInsetsStateController.getRawInsetsState(),
+ mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
calculateRoundedCornersForRotation(mDisplayInfo.rotation),
calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation));
@@ -1936,11 +1936,11 @@
private void startFixedRotationTransform(WindowToken token, int rotation) {
mTmpConfiguration.unset();
final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
- final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
+ final DisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final PrivacyIndicatorBounds indicatorBounds =
calculatePrivacyIndicatorBoundsForRotation(rotation);
- final DisplayFrames displayFrames = new DisplayFrames(mDisplayId, new InsetsState(), info,
+ final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
cutout, roundedCorners, indicatorBounds);
token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
}
@@ -2147,12 +2147,10 @@
final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
// Update application display metrics.
- final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
- final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+ final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
- final Rect appFrame = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation,
- wmDisplayCutout);
+ final Rect appFrame = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
mDisplayInfo.rotation = rotation;
mDisplayInfo.logicalWidth = dw;
mDisplayInfo.logicalHeight = dh;
@@ -2190,9 +2188,10 @@
return mDisplayInfo;
}
- WmDisplayCutout calculateDisplayCutoutForRotation(int rotation) {
+ DisplayCutout calculateDisplayCutoutForRotation(int rotation) {
return mDisplayCutoutCache.getOrCompute(
- mIsSizeForced ? mBaseDisplayCutout : mInitialDisplayCutout, rotation);
+ mIsSizeForced ? mBaseDisplayCutout : mInitialDisplayCutout, rotation)
+ .getDisplayCutout();
}
static WmDisplayCutout calculateDisplayCutoutForRotationAndDisplaySizeUncached(
@@ -2263,9 +2262,7 @@
final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
outConfig.windowConfiguration.setMaxBounds(0, 0, dw, dh);
outConfig.windowConfiguration.setBounds(outConfig.windowConfiguration.getMaxBounds());
-
- final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
- computeScreenAppConfiguration(outConfig, dw, dh, rotation, wmDisplayCutout);
+ computeScreenAppConfiguration(outConfig, dw, dh, rotation);
final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
displayInfo.rotation = rotation;
@@ -2274,7 +2271,7 @@
final Rect appBounds = outConfig.windowConfiguration.getAppBounds();
displayInfo.appWidth = appBounds.width();
displayInfo.appHeight = appBounds.height();
- final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+ final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh,
mDisplayMetrics.density, outConfig);
@@ -2283,21 +2280,17 @@
/** Compute configuration related to application without changing current display. */
private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh,
- int rotation, WmDisplayCutout wmDisplayCutout) {
- final DisplayFrames displayFrames =
- mDisplayPolicy.getSimulatedDisplayFrames(rotation, dw, dh, wmDisplayCutout);
- final Rect appFrame =
- mDisplayPolicy.getNonDecorDisplayFrameWithSimulatedFrame(displayFrames);
+ int rotation) {
+ final DisplayPolicy.DecorInsets.Info info =
+ mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh);
// AppBounds at the root level should mirror the app screen size.
- outConfig.windowConfiguration.setAppBounds(appFrame);
+ outConfig.windowConfiguration.setAppBounds(info.mNonDecorFrame);
outConfig.windowConfiguration.setRotation(rotation);
outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
final float density = mDisplayMetrics.density;
- final Point configSize =
- mDisplayPolicy.getConfigDisplaySizeWithSimulatedFrame(displayFrames);
- outConfig.screenWidthDp = (int) (configSize.x / density + 0.5f);
- outConfig.screenHeightDp = (int) (configSize.y / density + 0.5f);
+ outConfig.screenWidthDp = (int) (info.mConfigFrame.width() / density + 0.5f);
+ outConfig.screenHeightDp = (int) (info.mConfigFrame.height() / density + 0.5f);
outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
@@ -2320,8 +2313,7 @@
config.windowConfiguration.setWindowingMode(getWindowingMode());
config.windowConfiguration.setDisplayWindowingMode(getWindowingMode());
- computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation,
- calculateDisplayCutoutForRotation(getRotation()));
+ computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation);
config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
| ((displayInfo.flags & Display.FLAG_ROUND) != 0
@@ -2430,9 +2422,8 @@
private int reduceCompatConfigWidthSize(int curSize, int rotation,
DisplayMetrics dm, int dw, int dh) {
- final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
- final Rect nonDecorSize = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation,
- wmDisplayCutout);
+ final Rect nonDecorSize =
+ mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
dm.noncompatWidthPixels = nonDecorSize.width();
dm.noncompatHeightPixels = nonDecorSize.height();
float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
@@ -2481,11 +2472,8 @@
}
private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh) {
- // Get the display cutout at this rotation.
- final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
-
// Get the app screen size at this rotation.
- final Rect size = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, wmDisplayCutout);
+ final Rect size = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
// Compute the screen layout size class for this rotation.
int longSize = size.width();
@@ -2501,19 +2489,21 @@
}
private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
- final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
- final Point size = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, wmDisplayCutout);
- if (size.x < displayInfo.smallestNominalAppWidth) {
- displayInfo.smallestNominalAppWidth = size.x;
+ final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
+ rotation, dw, dh);
+ final int w = info.mConfigFrame.width();
+ final int h = info.mConfigFrame.height();
+ if (w < displayInfo.smallestNominalAppWidth) {
+ displayInfo.smallestNominalAppWidth = w;
}
- if (size.x > displayInfo.largestNominalAppWidth) {
- displayInfo.largestNominalAppWidth = size.x;
+ if (w > displayInfo.largestNominalAppWidth) {
+ displayInfo.largestNominalAppWidth = w;
}
- if (size.y < displayInfo.smallestNominalAppHeight) {
- displayInfo.smallestNominalAppHeight = size.y;
+ if (h < displayInfo.smallestNominalAppHeight) {
+ displayInfo.smallestNominalAppHeight = h;
}
- if (size.y > displayInfo.largestNominalAppHeight) {
- displayInfo.largestNominalAppHeight = size.y;
+ if (h > displayInfo.largestNominalAppHeight) {
+ displayInfo.largestNominalAppHeight = h;
}
}
@@ -2773,14 +2763,19 @@
}
private void updateDisplayFrames(boolean notifyInsetsChange) {
- if (mDisplayFrames.update(mDisplayInfo,
- calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
- calculateRoundedCornersForRotation(mDisplayInfo.rotation),
- calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation))) {
+ if (updateDisplayFrames(mDisplayFrames, mDisplayInfo.rotation,
+ mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
mInsetsStateController.onDisplayFramesUpdated(notifyInsetsChange);
}
}
+ boolean updateDisplayFrames(DisplayFrames displayFrames, int rotation, int w, int h) {
+ return displayFrames.update(rotation, w, h,
+ calculateDisplayCutoutForRotation(rotation),
+ calculateRoundedCornersForRotation(rotation),
+ calculatePrivacyIndicatorBoundsForRotation(rotation));
+ }
+
@Override
void onDisplayChanged(DisplayContent dc) {
super.onDisplayChanged(dc);
@@ -2943,6 +2938,9 @@
+ mBaseDisplayHeight + " on display:" + getDisplayId());
}
}
+ if (mDisplayReady) {
+ mDisplayPolicy.mDecorInsets.invalidate();
+ }
}
/**
@@ -5584,7 +5582,7 @@
outExclusionUnrestricted.setEmpty();
}
final Region unhandled = Region.obtain();
- unhandled.set(0, 0, mDisplayFrames.mDisplayWidth, mDisplayFrames.mDisplayHeight);
+ unhandled.set(0, 0, mDisplayFrames.mWidth, mDisplayFrames.mHeight);
final Rect leftEdge = mInsetsStateController.getSourceProvider(ITYPE_LEFT_GESTURES)
.getSource().getFrame();
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 7ca38b8..33641f7 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -30,8 +30,6 @@
import android.view.PrivacyIndicatorBounds;
import android.view.RoundedCorners;
-import com.android.server.wm.utils.WmDisplayCutout;
-
import java.io.PrintWriter;
/**
@@ -39,8 +37,6 @@
* @hide
*/
public class DisplayFrames {
- public final int mDisplayId;
-
public final InsetsState mInsetsState;
/**
@@ -54,48 +50,45 @@
*/
public final Rect mDisplayCutoutSafe = new Rect();
- public int mDisplayWidth;
- public int mDisplayHeight;
+ public int mWidth;
+ public int mHeight;
public int mRotation;
- public DisplayFrames(int displayId, InsetsState insetsState, DisplayInfo info,
- WmDisplayCutout displayCutout, RoundedCorners roundedCorners,
- PrivacyIndicatorBounds indicatorBounds) {
- mDisplayId = displayId;
+ public DisplayFrames(InsetsState insetsState, DisplayInfo info, DisplayCutout cutout,
+ RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds) {
mInsetsState = insetsState;
- update(info, displayCutout, roundedCorners, indicatorBounds);
+ update(info.rotation, info.logicalWidth, info.logicalHeight, cutout, roundedCorners,
+ indicatorBounds);
+ }
+
+ DisplayFrames() {
+ mInsetsState = new InsetsState();
}
/**
- * This is called when {@link DisplayInfo} or {@link PrivacyIndicatorBounds} is updated.
+ * This is called if the display info may be changed, e.g. rotation, size, insets.
*
- * @param info the updated {@link DisplayInfo}.
- * @param displayCutout the updated {@link DisplayCutout}.
- * @param roundedCorners the updated {@link RoundedCorners}.
- * @param indicatorBounds the updated {@link PrivacyIndicatorBounds}.
* @return {@code true} if anything has been changed; {@code false} otherwise.
*/
- public boolean update(DisplayInfo info, @NonNull WmDisplayCutout displayCutout,
+ public boolean update(int rotation, int w, int h, @NonNull DisplayCutout displayCutout,
@NonNull RoundedCorners roundedCorners,
@NonNull PrivacyIndicatorBounds indicatorBounds) {
final InsetsState state = mInsetsState;
final Rect safe = mDisplayCutoutSafe;
- final DisplayCutout cutout = displayCutout.getDisplayCutout();
- if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight
- && mRotation == info.rotation
- && state.getDisplayCutout().equals(cutout)
+ if (mRotation == rotation && mWidth == w && mHeight == h
+ && mInsetsState.getDisplayCutout().equals(displayCutout)
&& state.getRoundedCorners().equals(roundedCorners)
&& state.getPrivacyIndicatorBounds().equals(indicatorBounds)) {
return false;
}
- mDisplayWidth = info.logicalWidth;
- mDisplayHeight = info.logicalHeight;
- mRotation = info.rotation;
+ mRotation = rotation;
+ mWidth = w;
+ mHeight = h;
final Rect unrestricted = mUnrestricted;
- unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight);
+ unrestricted.set(0, 0, w, h);
state.setDisplayFrame(unrestricted);
- state.setDisplayCutout(cutout);
+ state.setDisplayCutout(displayCutout);
state.setRoundedCorners(roundedCorners);
state.setPrivacyIndicatorBounds(indicatorBounds);
state.getDisplayCutoutSafe(safe);
@@ -132,7 +125,6 @@
}
public void dump(String prefix, PrintWriter pw) {
- pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
- + " r=" + mRotation);
+ pw.println(prefix + "DisplayFrames w=" + mWidth + " h=" + mHeight + " r=" + mRotation);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 713c13e..fe7cb19 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -19,19 +19,15 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.TYPE_INTERNAL;
-import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -107,7 +103,6 @@
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PixelFormat;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.gui.DropInputMode;
@@ -132,8 +127,6 @@
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
import android.view.InsetsVisibilities;
-import android.view.PrivacyIndicatorBounds;
-import android.view.RoundedCorners;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
@@ -151,7 +144,6 @@
import com.android.internal.policy.ForceShowNavBarSettingsObserver;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.policy.SystemBarUtils;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.util.ScreenshotHelper;
@@ -166,7 +158,6 @@
import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
-import com.android.server.wm.utils.WmDisplayCutout;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -242,6 +233,8 @@
private final SystemGesturesPointerEventListener mSystemGestures;
+ final DecorInsets mDecorInsets;
+
private volatile int mLidState = LID_ABSENT;
private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private volatile boolean mHdmiPlugged;
@@ -266,7 +259,6 @@
private WindowState mStatusBar = null;
private volatile WindowState mNotificationShade;
- private final int[] mStatusBarHeightForRotation = new int[4];
private WindowState mNavigationBar = null;
@NavigationBarPosition
private int mNavigationBarPosition = NAV_BAR_BOTTOM;
@@ -353,7 +345,6 @@
private static final Rect sTmpRect = new Rect();
private static final Rect sTmpRect2 = new Rect();
- private static final Rect sTmpLastParentFrame = new Rect();
private static final Rect sTmpDisplayCutoutSafe = new Rect();
private static final ClientWindowFrames sTmpClientFrames = new ClientWindowFrames();
@@ -391,16 +382,6 @@
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
- // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
- private static final int[] STABLE_TYPES = new int[]{
- ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT,
- ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR
- };
- private static final int[] NON_DECOR_TYPES = new int[]{
- ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT,
- ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR
- };
-
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;
@@ -444,6 +425,7 @@
: service.mAtmService.mSystemThread
.getSystemUiContext(displayContent.getDisplayId());
mDisplayContent = displayContent;
+ mDecorInsets = new DecorInsets(displayContent);
mLock = service.getWindowManagerLock();
final int displayId = displayContent.getDisplayId();
@@ -1227,7 +1209,7 @@
Math.max(displayFrames.mDisplayCutoutSafe.left, 0);
inOutFrame.left = 0;
inOutFrame.top = 0;
- inOutFrame.bottom = displayFrames.mDisplayHeight;
+ inOutFrame.bottom = displayFrames.mHeight;
inOutFrame.right = leftSafeInset + mLeftGestureInset;
});
mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win,
@@ -1237,8 +1219,8 @@
displayFrames.mUnrestricted.right);
inOutFrame.left = rightSafeInset - mRightGestureInset;
inOutFrame.top = 0;
- inOutFrame.bottom = displayFrames.mDisplayHeight;
- inOutFrame.right = displayFrames.mDisplayWidth;
+ inOutFrame.bottom = displayFrames.mHeight;
+ inOutFrame.right = displayFrames.mWidth;
});
mDisplayContent.setInsetProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT, win,
(displayFrames, windowContainer, inOutFrame) -> {
@@ -1429,11 +1411,6 @@
return Math.max(statusBarHeight, displayFrames.mDisplayCutoutSafe.top);
}
- @VisibleForTesting
- int getStatusBarHeightForRotation(@Surface.Rotation int rotation) {
- return SystemBarUtils.getStatusBarHeightForRotation(mUiContext, rotation);
- }
-
WindowState getStatusBar() {
return mStatusBar != null ? mStatusBar : mStatusBarAlt;
}
@@ -1929,25 +1906,11 @@
final Resources res = getCurrentUserResources();
final int portraitRotation = displayRotation.getPortraitRotation();
- final int upsideDownRotation = displayRotation.getUpsideDownRotation();
- final int landscapeRotation = displayRotation.getLandscapeRotation();
- final int seascapeRotation = displayRotation.getSeascapeRotation();
if (hasStatusBar()) {
- mStatusBarHeightForRotation[portraitRotation] =
- mStatusBarHeightForRotation[upsideDownRotation] =
- getStatusBarHeightForRotation(portraitRotation);
- mStatusBarHeightForRotation[landscapeRotation] =
- getStatusBarHeightForRotation(landscapeRotation);
- mStatusBarHeightForRotation[seascapeRotation] =
- getStatusBarHeightForRotation(seascapeRotation);
mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
R.dimen.display_cutout_touchable_region_size);
} else {
- mStatusBarHeightForRotation[portraitRotation] =
- mStatusBarHeightForRotation[upsideDownRotation] =
- mStatusBarHeightForRotation[landscapeRotation] =
- mStatusBarHeightForRotation[seascapeRotation] = 0;
mDisplayCutoutTouchableRegionSize = 0;
}
@@ -2053,33 +2016,9 @@
}
/**
- * Return the display frame available after excluding any screen decorations that could never be
- * removed in Honeycomb. That is, system bar or button bar.
- *
- * @return display frame excluding all non-decor insets.
- */
- Rect getNonDecorDisplayFrame(int fullWidth, int fullHeight, int rotation,
- WmDisplayCutout cutout) {
- final DisplayFrames displayFrames =
- getSimulatedDisplayFrames(rotation, fullWidth, fullHeight, cutout);
- return getNonDecorDisplayFrameWithSimulatedFrame(displayFrames);
- }
-
- Rect getNonDecorDisplayFrameWithSimulatedFrame(DisplayFrames displayFrames) {
- final Rect nonDecorInsets =
- getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect();
- final Rect displayFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame());
- displayFrame.inset(nonDecorInsets);
- return displayFrame;
- }
-
- /**
* Get the Navigation Bar Frame height. This dimension is the height of the navigation bar that
* is used for spacing to show additional buttons on the navigation bar (such as the ime
- * switcher when ime is visible) while {@link #getNavigationBarHeight} is used for the visible
- * height that we send to the app as content insets that can be smaller.
- * <p>
- * In car mode it will return the same height as {@link #getNavigationBarHeight}
+ * switcher when ime is visible).
*
* @param rotation specifies rotation to return dimension from
* @return navigation bar frame height
@@ -2092,26 +2031,6 @@
}
/**
- * Return the available screen size that we should report for the
- * configuration. This must be no larger than
- * {@link #getNonDecorDisplayFrame(int, int, int, DisplayCutout)}; it may be smaller
- * than that to account for more transient decoration like a status bar.
- */
- public Point getConfigDisplaySize(int fullWidth, int fullHeight, int rotation,
- WmDisplayCutout wmDisplayCutout) {
- final DisplayFrames displayFrames = getSimulatedDisplayFrames(rotation, fullWidth,
- fullHeight, wmDisplayCutout);
- return getConfigDisplaySizeWithSimulatedFrame(displayFrames);
- }
-
- Point getConfigDisplaySizeWithSimulatedFrame(DisplayFrames displayFrames) {
- final Insets insets = getInsetsWithInternalTypes(displayFrames, STABLE_TYPES);
- Rect configFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame());
- configFrame.inset(insets);
- return new Point(configFrame.width(), configFrame.height());
- }
-
- /**
* Return corner radius in pixels that should be used on windows in order to cover the display.
*
* The radius is only valid for internal displays, since the corner radius of external displays
@@ -2126,89 +2045,152 @@
return mShowingDream;
}
- /**
- * Calculates the stable insets if we already have the non-decor insets.
- *
- * @param inOutInsets The known non-decor insets. It will be modified to stable insets.
- * @param rotation The current display rotation.
- */
- void convertNonDecorInsetsToStableInsets(Rect inOutInsets, int rotation) {
- inOutInsets.top = Math.max(inOutInsets.top, mStatusBarHeightForRotation[rotation]);
+ /** The latest insets and frames for screen configuration calculation. */
+ static class DecorInsets {
+ static class Info {
+ /**
+ * The insets for the areas that could never be removed, i.e. display cutout and
+ * navigation bar. Note that its meaning is actually "decor insets". The "non" is just
+ * because it is used to calculate {@link #mNonDecorFrame}.
+ */
+ final Rect mNonDecorInsets = new Rect();
+
+ /**
+ * The stable insets that can affect configuration. The sources are usually from
+ * display cutout, navigation bar, and status bar.
+ */
+ final Rect mConfigInsets = new Rect();
+
+ /** The display frame available after excluding {@link #mNonDecorInsets}. */
+ final Rect mNonDecorFrame = new Rect();
+
+ /**
+ * The available (stable) screen size that we should report for the configuration.
+ * This must be no larger than {@link #mNonDecorFrame}; it may be smaller than that
+ * to account for more transient decoration like a status bar.
+ */
+ final Rect mConfigFrame = new Rect();
+
+ private boolean mNeedUpdate = true;
+
+ void update(DisplayContent dc, int rotation, int w, int h) {
+ final DisplayFrames df = new DisplayFrames();
+ dc.updateDisplayFrames(df, rotation, w, h);
+ dc.getDisplayPolicy().simulateLayoutDisplay(df);
+ final InsetsState insetsState = df.mInsetsState;
+ final Rect displayFrame = insetsState.getDisplayFrame();
+ final Insets decor = calculateDecorInsetsWithInternalTypes(insetsState);
+ final Insets statusBar = insetsState.calculateInsets(displayFrame,
+ Type.statusBars(), true /* ignoreVisibility */);
+ mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
+ mConfigInsets.set(Math.max(statusBar.left, decor.left),
+ Math.max(statusBar.top, decor.top),
+ Math.max(statusBar.right, decor.right),
+ Math.max(statusBar.bottom, decor.bottom));
+ mNonDecorFrame.set(displayFrame);
+ mNonDecorFrame.inset(mNonDecorInsets);
+ mConfigFrame.set(displayFrame);
+ mConfigFrame.inset(mConfigInsets);
+ mNeedUpdate = false;
+ }
+
+ void set(Info other) {
+ mNonDecorInsets.set(other.mNonDecorInsets);
+ mConfigInsets.set(other.mConfigInsets);
+ mNonDecorFrame.set(other.mNonDecorFrame);
+ mConfigFrame.set(other.mConfigFrame);
+ mNeedUpdate = false;
+ }
+
+ @Override
+ public String toString() {
+ return "{nonDecorInsets=" + mNonDecorInsets
+ + ", configInsets=" + mConfigInsets
+ + ", nonDecorFrame=" + mNonDecorFrame
+ + ", configFrame=" + mConfigFrame + '}';
+ }
+ }
+
+ // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
+ static final int[] INTERNAL_DECOR_TYPES;
+ static {
+ final ArraySet<Integer> decorTypes = InsetsState.toInternalType(
+ Type.displayCutout() | Type.navigationBars());
+ decorTypes.remove(ITYPE_EXTRA_NAVIGATION_BAR);
+ INTERNAL_DECOR_TYPES = new int[decorTypes.size()];
+ for (int i = 0; i < INTERNAL_DECOR_TYPES.length; i++) {
+ INTERNAL_DECOR_TYPES[i] = decorTypes.valueAt(i);
+ }
+ }
+
+ private final DisplayContent mDisplayContent;
+ private final Info[] mInfoForRotation = new Info[4];
+ final Info mTmpInfo = new Info();
+
+ DecorInsets(DisplayContent dc) {
+ mDisplayContent = dc;
+ for (int i = mInfoForRotation.length - 1; i >= 0; i--) {
+ mInfoForRotation[i] = new Info();
+ }
+ }
+
+ Info get(int rotation, int w, int h) {
+ final Info info = mInfoForRotation[rotation];
+ if (info.mNeedUpdate) {
+ info.update(mDisplayContent, rotation, w, h);
+ }
+ return info;
+ }
+
+ /** Called when the screen decor insets providers have changed. */
+ void invalidate() {
+ for (Info info : mInfoForRotation) {
+ info.mNeedUpdate = true;
+ }
+ }
+
+ // TODO (b/235842600): Remove this method once we can treat task bar as navigation bar.
+ private static Insets calculateDecorInsetsWithInternalTypes(InsetsState state) {
+ final Rect frame = state.getDisplayFrame();
+ Insets insets = Insets.NONE;
+ for (int i = INTERNAL_DECOR_TYPES.length - 1; i >= 0; i--) {
+ final InsetsSource source = state.peekSource(INTERNAL_DECOR_TYPES[i]);
+ if (source != null) {
+ insets = Insets.max(source.calculateInsets(frame, true /* ignoreVisibility */),
+ insets);
+ }
+ }
+ return insets;
+ }
}
/**
- * Calculates the stable insets without running a layout.
- *
- * @param displayRotation the current display rotation
- * @param displayWidth full display width
- * @param displayHeight full display height
- * @param displayCutout the current display cutout
- * @param outInsets the insets to return
+ * If the decor insets changes, the display configuration may be affected. The caller should
+ * call {@link DisplayContent#sendNewConfiguration()} if this method returns {@code true}.
*/
- public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
- WmDisplayCutout displayCutout, Rect outInsets) {
- final DisplayFrames displayFrames = getSimulatedDisplayFrames(displayRotation,
- displayWidth, displayHeight, displayCutout);
- getStableInsetsWithSimulatedFrame(displayFrames, outInsets);
+ boolean updateDecorInsetsInfoIfNeeded(WindowState win) {
+ if (!win.providesNonDecorInsets()) {
+ return false;
+ }
+ final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
+ final int rotation = displayFrames.mRotation;
+ final int dw = displayFrames.mWidth;
+ final int dh = displayFrames.mHeight;
+ final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo;
+ newInfo.update(mDisplayContent, rotation, dw, dh);
+ final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
+ if (newInfo.mNonDecorFrame.equals(currentInfo.mNonDecorFrame)) {
+ return false;
+ }
+ mDecorInsets.invalidate();
+ mDecorInsets.mInfoForRotation[rotation].set(newInfo);
+ // If the device is booting, let the boot procedure trigger the new configuration.
+ // Otherwise the display configuration needs to be recomputed now.
+ return mService.mDisplayEnabled;
}
- void getStableInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) {
- // Navigation bar, status bar, and cutout.
- outInsets.set(getInsetsWithInternalTypes(displayFrames, STABLE_TYPES).toRect());
- }
-
- /**
- * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
- * bar or button bar. See {@link #getNonDecorDisplayFrame}.
- *
- * @param displayRotation the current display rotation
- * @param fullWidth the width of the display, including all insets
- * @param fullHeight the height of the display, including all insets
- * @param cutout the current display cutout
- * @param outInsets the insets to return
- */
- public void getNonDecorInsetsLw(int displayRotation, int fullWidth, int fullHeight,
- WmDisplayCutout cutout, Rect outInsets) {
- final DisplayFrames displayFrames =
- getSimulatedDisplayFrames(displayRotation, fullWidth, fullHeight, cutout);
- getNonDecorInsetsWithSimulatedFrame(displayFrames, outInsets);
- }
-
- void getNonDecorInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) {
- outInsets.set(getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect());
- }
-
- DisplayFrames getSimulatedDisplayFrames(int displayRotation, int fullWidth,
- int fullHeight, WmDisplayCutout cutout) {
- final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
- info.rotation = displayRotation;
- info.logicalWidth = fullWidth;
- info.logicalHeight = fullHeight;
- info.displayCutout = cutout.getDisplayCutout();
- final RoundedCorners roundedCorners =
- mDisplayContent.calculateRoundedCornersForRotation(displayRotation);
- final PrivacyIndicatorBounds indicatorBounds =
- mDisplayContent.calculatePrivacyIndicatorBoundsForRotation(displayRotation);
- final DisplayFrames displayFrames = new DisplayFrames(getDisplayId(), new InsetsState(),
- info, cutout, roundedCorners, indicatorBounds);
- simulateLayoutDisplay(displayFrames);
- return displayFrames;
- }
-
- @VisibleForTesting
- Insets getInsets(DisplayFrames displayFrames, @InsetsType int type) {
- final InsetsState state = displayFrames.mInsetsState;
- final Insets insets = state.calculateInsets(state.getDisplayFrame(), type,
- true /* ignoreVisibility */);
- return insets;
- }
-
- Insets getInsetsWithInternalTypes(DisplayFrames displayFrames,
- @InternalInsetsType int[] types) {
- final InsetsState state = displayFrames.mInsetsState;
- final Insets insets = state.calculateInsetsWithInternalTypes(state.getDisplayFrame(), types,
- true /* ignoreVisibility */);
- return insets;
+ DecorInsets.Info getDecorInsetsInfo(int rotation, int w, int h) {
+ return mDecorInsets.get(rotation, w, h);
}
@NavigationBarPosition
@@ -2851,6 +2833,11 @@
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
+ pw.print(prefix); pw.println("mDecorInsetsInfo:");
+ for (int rotation = 0; rotation < mDecorInsets.mInfoForRotation.length; rotation++) {
+ final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation];
+ pw.println(prefixInner + Surface.rotationToString(rotation) + "=" + info);
+ }
mSystemGestures.dump(pw, prefix);
pw.print(prefix); pw.println("Looper state:");
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ed771c2..ac1fbc3 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -34,6 +34,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
+import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
@@ -44,6 +45,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Manages global window inset state in the system represented by {@link InsetsState}.
@@ -86,8 +88,17 @@
}
};
+ private final Function<Integer, WindowContainerInsetsSourceProvider> mSourceProviderFunc;
+
InsetsStateController(DisplayContent displayContent) {
mDisplayContent = displayContent;
+ mSourceProviderFunc = type -> {
+ final InsetsSource source = mState.getSource(type);
+ if (type == ITYPE_IME) {
+ return new ImeInsetsSourceProvider(source, this, mDisplayContent);
+ }
+ return new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
+ };
}
InsetsState getRawInsetsState() {
@@ -115,15 +126,7 @@
* @return The provider of a specific type.
*/
WindowContainerInsetsSourceProvider getSourceProvider(@InternalInsetsType int type) {
- if (type == ITYPE_IME) {
- return mProviders.computeIfAbsent(type,
- key -> new ImeInsetsSourceProvider(
- mState.getSource(key), this, mDisplayContent));
- } else {
- return mProviders.computeIfAbsent(type,
- key -> new WindowContainerInsetsSourceProvider(mState.getSource(key), this,
- mDisplayContent));
- }
+ return mProviders.computeIfAbsent(type, mSourceProviderFunc);
}
ImeInsetsSourceProvider getImeSourceProvider() {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 32fa420..036a292 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -98,7 +98,6 @@
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.am.HostingRecord;
import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.wm.utils.WmDisplayCutout;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -298,10 +297,11 @@
final Point mLastSurfaceSize = new Point();
- private final Rect mTmpInsets = new Rect();
private final Rect mTmpBounds = new Rect();
private final Rect mTmpFullBounds = new Rect();
+ /** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */
private final Rect mTmpStableBounds = new Rect();
+ /** For calculating app bounds, i.e. the area without the nav bar and display cutout. */
private final Rect mTmpNonDecorBounds = new Rect();
//TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
@@ -2202,21 +2202,16 @@
DisplayInfo displayInfo) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
- final Task rootTask = getRootTaskFragment().asTask();
- if (rootTask == null || rootTask.mDisplayContent == null) {
+ if (mDisplayContent == null) {
return;
}
mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
- final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
- final WmDisplayCutout cutout =
- rootTask.mDisplayContent.calculateDisplayCutoutForRotation(displayInfo.rotation);
- final DisplayFrames displayFrames = policy.getSimulatedDisplayFrames(displayInfo.rotation,
- displayInfo.logicalWidth, displayInfo.logicalHeight, cutout);
- policy.getNonDecorInsetsWithSimulatedFrame(displayFrames, mTmpInsets);
- intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
- policy.getStableInsetsWithSimulatedFrame(displayFrames, mTmpInsets);
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
+ final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
+ displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 8e389d3..5495b30 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -80,6 +80,7 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -706,7 +707,11 @@
// remove the surfaces yet. If it is currently visible, but not expected-visible,
// then doing commitVisibility here would actually be out-of-order and leave the
// activity in a bad state.
- if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) {
+ // TODO (b/243755838) Create a screen off transition to correct the visible status
+ // of activities.
+ final boolean isScreenOff = ar.mDisplayContent == null
+ || ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF;
+ if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) {
final boolean commitVisibility = !checkEnterPipOnFinish(ar);
// Avoid commit visibility if entering pip or else we will get a sudden
// "flash" / surface going invisible for a split second.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6819833..cc17b5b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -319,7 +319,6 @@
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
-import com.android.server.wm.utils.WmDisplayCutout;
import dalvik.annotation.optimization.NeverCompile;
@@ -1857,7 +1856,7 @@
+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation())
- || win.providesNonDecorInsets()) {
+ || displayPolicy.updateDecorInsetsInfoIfNeeded(win)) {
displayContent.sendNewConfiguration();
}
@@ -2254,7 +2253,7 @@
Arrays.fill(outActiveControls, null);
}
int result = 0;
- boolean configChanged;
+ boolean configChanged = false;
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
@@ -2321,10 +2320,15 @@
flagChanges = win.mAttrs.flags ^ attrs.flags;
privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
attrChanges = win.mAttrs.copyFrom(attrs);
- if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED
- | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) {
+ final boolean layoutChanged =
+ (attrChanges & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0;
+ if (layoutChanged || (attrChanges
+ & WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED) != 0) {
win.mLayoutNeeded = true;
}
+ if (layoutChanged) {
+ configChanged = displayPolicy.updateDecorInsetsInfoIfNeeded(win);
+ }
if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
|| (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
win.mActivityRecord.checkKeyguardFlagsChanged();
@@ -2534,7 +2538,7 @@
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: updateOrientation");
- configChanged = displayContent.updateOrientation();
+ configChanged |= displayContent.updateOrientation();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (toBeDisplayed && win.mIsWallpaper) {
@@ -7177,9 +7181,8 @@
final DisplayContent dc = mRoot.getDisplayContent(displayId);
if (dc != null) {
final DisplayInfo di = dc.getDisplayInfo();
- final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(di.rotation);
- dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
- cutout, outInsets);
+ outInsets.set(dc.getDisplayPolicy().getDecorInsetsInfo(
+ di.rotation, di.logicalWidth, di.logicalHeight).mConfigInsets);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b601cc5..65db359 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2511,6 +2511,7 @@
mWinAnimator.mSurfaceController,
Debug.getCallers(5));
+ final DisplayContent displayContent = getDisplayContent();
final long origId = Binder.clearCallingIdentity();
try {
@@ -2565,7 +2566,7 @@
// Set up a replacement input channel since the app is now dead.
// We need to catch tapping on the dead window to restart the app.
openInputChannel(null);
- getDisplayContent().getInputMonitor().updateInputWindowsLw(true /*force*/);
+ displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
return;
}
@@ -2573,7 +2574,7 @@
// usually unnoticeable (e.g. covered by rotation animation) and the animation
// bounds could be inconsistent, such as depending on when the window applies
// its draw transaction with new rotation.
- final boolean allowExitAnimation = !getDisplayContent().inTransition()
+ final boolean allowExitAnimation = !displayContent.inTransition()
// There will be a new window so the exit animation may not be visible or
// look weird if its orientation is changed.
&& !inRelaunchingActivity();
@@ -2623,18 +2624,11 @@
}
removeImmediately();
- boolean sentNewConfig = false;
- if (wasVisible) {
- // Removing a visible window will effect the computed orientation
- // So just update orientation if needed.
- final DisplayContent displayContent = getDisplayContent();
- if (displayContent.updateOrientation()) {
- displayContent.sendNewConfiguration();
- sentNewConfig = true;
- }
- }
- if (!sentNewConfig && providesNonDecorInsets()) {
- getDisplayContent().sendNewConfiguration();
+ // Removing a visible window may affect the display orientation so just update it if
+ // needed. Also recompute configuration if it provides screen decor insets.
+ if ((wasVisible && displayContent.updateOrientation())
+ || displayContent.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(this)) {
+ displayContent.sendNewConfiguration();
}
mWmService.updateFocusedWindowLocked(isFocused()
? UPDATE_FOCUS_REMOVING_FOCUS
diff --git a/services/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
deleted file mode 100644
index 477625a..0000000
--- a/services/core/xsd/display-device-config/autobrightness.xsd
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<xs:schema version="2.0"
- elementFormDefault="qualified"
- xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:complexType name="autoBrightness">
- <xs:sequence>
- <!-- Sets the debounce for autoBrightness brightening in millis-->
- <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- <!-- Sets the debounce for autoBrightness darkening in millis-->
- <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- </xs:sequence>
- </xs:complexType>
-</xs:schema>
\ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index bea5e2c..6b05d8f 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,7 +23,6 @@
<xs:schema version="2.0"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:include schemaLocation="autobrightness.xsd" />
<xs:element name="displayConfiguration">
<xs:complexType>
<xs:sequence>
@@ -343,4 +342,74 @@
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
+
+ <xs:complexType name="autoBrightness">
+ <xs:sequence>
+ <!-- Sets the debounce for autoBrightness brightening in millis-->
+ <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the debounce for autoBrightness darkening in millis-->
+ <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the brightness mapping of the desired screen brightness in nits to the
+ corresponding lux for the current display -->
+ <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- Represents the brightness mapping of the desired screen brightness in nits to the
+ corresponding lux for the current display -->
+ <xs:complexType name="displayBrightnessMapping">
+ <xs:sequence>
+ <!-- Sets the list of display brightness points, each representing the desired screen
+ brightness in nits to the corresponding lux for the current display
+
+ The N entries of this array define N + 1 control points as follows:
+ (1-based arrays)
+
+ Point 1: (0, nits[1]): currentLux <= 0
+ Point 2: (lux[1], nits[2]): 0 < currentLux <= lux[1]
+ Point 3: (lux[2], nits[3]): lux[2] < currentLux <= lux[3]
+ ...
+ Point N+1: (lux[N], nits[N+1]): lux[N] < currentLux
+
+ The control points must be strictly increasing. Each control point
+ corresponds to an entry in the brightness backlight values arrays.
+ For example, if currentLux == lux[1] (first element of the levels array)
+ then the brightness will be determined by nits[2] (second element
+ of the brightness values array).
+ -->
+ <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
+ minOccurs="1" maxOccurs="unbounded">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- Represents a point in the display brightness mapping, representing the lux level from the
+ light sensor to the desired screen brightness in nits at this level -->
+ <xs:complexType name="displayBrightnessPoint">
+ <xs:sequence>
+ <!-- The lux level from the light sensor. This must be a non-negative integer -->
+ <xs:element name="lux" type="xs:nonNegativeInteger"
+ minOccurs="1" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+
+ <!-- Desired screen brightness in nits corresponding to the suggested lux values.
+ The display brightness is defined as the measured brightness of an all-white image.
+ This must be a non-negative integer -->
+ <xs:element name="nits" type="nonNegativeDecimal"
+ minOccurs="1" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index e9a9269..fb7a920 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -5,8 +5,10 @@
ctor public AutoBrightness();
method public final java.math.BigInteger getBrighteningLightDebounceMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
+ method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
+ method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
}
public class BrightnessThresholds {
@@ -43,6 +45,19 @@
method public java.util.List<com.android.server.display.config.Density> getDensity();
}
+ public class DisplayBrightnessMapping {
+ ctor public DisplayBrightnessMapping();
+ method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
+ }
+
+ public class DisplayBrightnessPoint {
+ ctor public DisplayBrightnessPoint();
+ method public final java.math.BigInteger getLux();
+ method public final java.math.BigDecimal getNits();
+ method public final void setLux(java.math.BigInteger);
+ method public final void setNits(java.math.BigDecimal);
+ }
+
public class DisplayConfiguration {
ctor public DisplayConfiguration();
method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 617321b..9c615d1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -149,6 +149,12 @@
.thenReturn(mockArray);
when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
.thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+ .thenReturn(mockArray);
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevels))
+ .thenReturn(new int[]{});
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 03ea613..66420ad 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -19,16 +19,19 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,22 +55,16 @@
private Resources mResources;
@Before
- public void setUp() throws IOException {
+ public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getResources()).thenReturn(mResources);
mockDeviceConfigs();
- try {
- Path tempFile = Files.createTempFile("display_config", ".tmp");
- Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
- mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
- mDisplayDeviceConfig.initFromFile(tempFile.toFile());
- } catch (IOException e) {
- throw new IOException("Failed to setup the display device config.", e);
- }
}
@Test
- public void testConfigValues() {
+ public void testConfigValuesFromDisplayConfig() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
@@ -88,10 +85,23 @@
assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
-
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
+ float[]{0.0f, 50.0f, 80.0f}, 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
+ float[]{45.32f, 75.43f}, 0.0f);
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
- // Also add test for the case where optional display configs are null
+ }
+
+ @Test
+ public void testConfigValuesFromConfigResource() {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
+ float[]{2.0f, 200.0f, 600.0f}, 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
+ float[]{0.0f, 0.0f, 110.0f, 500.0f}, 0.0f);
+ // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
+ // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
private String getContent() {
@@ -114,6 +124,16 @@
+ "<autoBrightness>\n"
+ "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
+ "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
+ + "<displayBrightnessMapping>\n"
+ + "<displayBrightnessPoint>\n"
+ + "<lux>50</lux>\n"
+ + "<nits>45.32</nits>\n"
+ + "</displayBrightnessPoint>\n"
+ + "<displayBrightnessPoint>\n"
+ + "<lux>80</lux>\n"
+ + "<nits>75.43</nits>\n"
+ + "</displayBrightnessPoint>\n"
+ + "</displayBrightnessMapping>\n"
+ "</autoBrightness>\n"
+ "<highBrightnessMode enabled=\"true\">\n"
+ "<transitionPoint>0.62</transitionPoint>\n"
@@ -185,4 +205,63 @@
when(mResources.getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
}
+
+ private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
+ Path tempFile = Files.createTempFile("display_config", ".tmp");
+ Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+ mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+ }
+
+ private void setupDisplayDeviceConfigFromConfigResourceFile() {
+ TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
+ when(mResources.obtainTypedArray(
+ com.android.internal.R.array.config_screenBrightnessNits))
+ .thenReturn(screenBrightnessNits);
+ TypedArray screenBrightnessBacklight = createFloatTypedArray(new
+ float[]{0.0f, 120.0f, 255.0f});
+ when(mResources.obtainTypedArray(
+ com.android.internal.R.array.config_screenBrightnessBacklight))
+ .thenReturn(screenBrightnessBacklight);
+ when(mResources.getIntArray(com.android.internal.R.array
+ .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
+
+ when(mResources.getIntArray(com.android.internal.R.array
+ .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
+ when(mResources.getIntArray(com.android.internal.R.array
+ .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
+
+ TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
+ float[]{2.0f, 200.0f, 600.0f});
+ when(mResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+ .thenReturn(screenBrightnessLevelNits);
+ int[] screenBrightnessLevelLux = new int[]{0, 110, 500};
+ when(mResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevels))
+ .thenReturn(screenBrightnessLevelLux);
+
+ mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+
+ }
+
+ private TypedArray createFloatTypedArray(float[] vals) {
+ TypedArray mockArray = mock(TypedArray.class);
+ when(mockArray.length()).thenAnswer(invocation -> {
+ return vals.length;
+ });
+ when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+ final float def = (float) invocation.getArguments()[1];
+ if (vals == null) {
+ return def;
+ }
+ int idx = (int) invocation.getArguments()[0];
+ if (idx >= 0 && idx < vals.length) {
+ return vals[idx];
+ } else {
+ return def;
+ }
+ });
+ return mockArray;
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e85d7db..a545c83 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7548,6 +7548,43 @@
}
@Test
+ public void testAddAutomaticZenRule_systemCallTakesPackageFromOwner() throws Exception {
+ mService.isSystemUid = true;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+ // verify that zen mode helper gets passed in a package name of "android"
+ verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+ }
+
+ @Test
+ public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
+ mService.isSystemUid = false;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "another.package");
+
+ // verify that zen mode helper gets passed in the package name from the arg, not the owner
+ verify(mockZenModeHelper).addAutomaticZenRule(
+ eq("another.package"), eq(rule), anyString());
+ }
+
+ @Test
public void testAreNotificationsEnabledForPackage() throws Exception {
mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
mUid);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index eb61a9c..bbd7695 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -158,7 +158,6 @@
import com.android.internal.R;
import com.android.server.wm.ActivityRecord.State;
-import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Assert;
import org.junit.Before;
@@ -552,9 +551,9 @@
final Rect insets = new Rect();
final DisplayInfo displayInfo = task.mDisplayContent.getDisplayInfo();
final DisplayPolicy policy = task.mDisplayContent.getDisplayPolicy();
- policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
- displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets);
- policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
+
+ insets.set(policy.getDecorInsetsInfo(displayInfo.rotation, displayInfo.logicalWidth,
+ displayInfo.logicalHeight).mConfigInsets);
Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
final boolean isScreenPortrait = stableRect.width() <= stableRect.height();
@@ -594,9 +593,8 @@
final Rect insets = new Rect();
final DisplayInfo displayInfo = rootTask.mDisplayContent.getDisplayInfo();
final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
- policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
- displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets);
- policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
+ insets.set(policy.getDecorInsetsInfo(displayInfo.rotation, displayInfo.logicalWidth,
+ displayInfo.logicalHeight).mConfigInsets);
Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
final boolean isScreenPortrait = stableRect.width() <= stableRect.height();
@@ -2988,31 +2986,27 @@
}
@Test
- public void testCloseToSquareFixedOrientationPortrait() {
+ public void testCloseToSquareFixedOrientation() {
// create a square display
final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
.setSystemDecorations(true).build();
+ // Add a decor insets provider window.
+ final WindowState navbar = createNavBarWithProvidedInsets(squareDisplay);
+ squareDisplay.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(navbar);
final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
// create a fixed portrait activity
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+ ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
- // check that both the configuration and app bounds are portrait
+ // The available space could be landscape because of decor insets, but the configuration
+ // should still respect the requested portrait orientation.
assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width()
<= activity.getConfiguration().windowConfiguration.getAppBounds().height());
- }
-
- @Test
- public void testCloseToSquareFixedOrientationLandscape() {
- // create a square display
- final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
- .setSystemDecorations(true).build();
- final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
// create a fixed landscape activity
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+ activity = new ActivityBuilder(mAtm).setTask(task)
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE).build();
// check that both the configuration and app bounds are landscape
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index a001eda..e298d51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -25,13 +25,10 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.util.Pair;
import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
-import com.android.server.wm.utils.WmDisplayCutout;
-
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
@@ -49,8 +46,7 @@
@Test
public void portrait() {
- final Pair<DisplayInfo, WmDisplayCutout> di =
- displayInfoForRotation(ROTATION_0, false /* withCutout */);
+ final DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */);
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
@@ -59,8 +55,7 @@
@Test
public void portrait_withCutout() {
- final Pair<DisplayInfo, WmDisplayCutout> di =
- displayInfoForRotation(ROTATION_0, true /* withCutout */);
+ final DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */);
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT);
@@ -69,8 +64,7 @@
@Test
public void landscape() {
- final Pair<DisplayInfo, WmDisplayCutout> di =
- displayInfoForRotation(ROTATION_90, false /* withCutout */);
+ final DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */);
if (mDisplayPolicy.navigationBarCanMove()) {
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
@@ -85,8 +79,7 @@
@Test
public void landscape_withCutout() {
- final Pair<DisplayInfo, WmDisplayCutout> di =
- displayInfoForRotation(ROTATION_90, true /* withCutout */);
+ final DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */);
if (mDisplayPolicy.navigationBarCanMove()) {
verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
@@ -101,8 +94,7 @@
@Test
public void seascape() {
- final Pair<DisplayInfo, WmDisplayCutout> di =
- displayInfoForRotation(ROTATION_270, false /* withCutout */);
+ final DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */);
if (mDisplayPolicy.navigationBarCanMove()) {
verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
@@ -117,8 +109,7 @@
@Test
public void seascape_withCutout() {
- final Pair<DisplayInfo, WmDisplayCutout> di =
- displayInfoForRotation(ROTATION_270, true /* withCutout */);
+ final DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */);
if (mDisplayPolicy.navigationBarCanMove()) {
verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
@@ -133,8 +124,7 @@
@Test
public void upsideDown() {
- final Pair<DisplayInfo, WmDisplayCutout> di =
- displayInfoForRotation(ROTATION_180, false /* withCutout */);
+ final DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */);
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
@@ -143,34 +133,32 @@
@Test
public void upsideDown_withCutout() {
- final Pair<DisplayInfo, WmDisplayCutout> di =
- displayInfoForRotation(ROTATION_180, true /* withCutout */);
+ final DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */);
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
verifyConsistency(di);
}
- private void verifyStableInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top,
+ private void verifyStableInsets(DisplayInfo di, int left, int top,
int right, int bottom) {
- mErrorCollector.checkThat("stableInsets", getStableInsetsLw(diPair.first, diPair.second),
+ mErrorCollector.checkThat("stableInsets", getStableInsets(di),
equalTo(new Rect(left, top, right, bottom)));
}
- private void verifyNonDecorInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top,
+ private void verifyNonDecorInsets(DisplayInfo di, int left, int top,
int right, int bottom) {
mErrorCollector.checkThat("nonDecorInsets",
- getNonDecorInsetsLw(diPair.first, diPair.second), equalTo(new Rect(
- left, top, right, bottom)));
+ getNonDecorInsets(di), equalTo(new Rect(left, top, right, bottom)));
}
- private void verifyConsistency(Pair<DisplayInfo, WmDisplayCutout> diPair) {
- final DisplayInfo di = diPair.first;
- final WmDisplayCutout cutout = diPair.second;
- verifyConsistency("configDisplay", di, getStableInsetsLw(di, cutout),
- getConfigDisplayWidth(di, cutout), getConfigDisplayHeight(di, cutout));
- verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di, cutout),
- getNonDecorDisplayWidth(di, cutout), getNonDecorDisplayHeight(di, cutout));
+ private void verifyConsistency(DisplayInfo di) {
+ final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
+ di.rotation, di.logicalWidth, di.logicalHeight);
+ verifyConsistency("configDisplay", di, info.mConfigInsets,
+ info.mConfigFrame.width(), info.mConfigFrame.height());
+ verifyConsistency("nonDecorDisplay", di, info.mNonDecorInsets,
+ info.mNonDecorFrame.width(), info.mNonDecorFrame.height());
}
private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width,
@@ -181,42 +169,18 @@
equalTo(di.logicalHeight - insets.top - insets.bottom));
}
- private Rect getStableInsetsLw(DisplayInfo di, WmDisplayCutout cutout) {
- Rect result = new Rect();
- mDisplayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
- cutout, result);
- return result;
+ private Rect getStableInsets(DisplayInfo di) {
+ return mDisplayPolicy.getDecorInsetsInfo(
+ di.rotation, di.logicalWidth, di.logicalHeight).mConfigInsets;
}
- private Rect getNonDecorInsetsLw(DisplayInfo di, WmDisplayCutout cutout) {
- Rect result = new Rect();
- mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
- cutout, result);
- return result;
+ private Rect getNonDecorInsets(DisplayInfo di) {
+ return mDisplayPolicy.getDecorInsetsInfo(
+ di.rotation, di.logicalWidth, di.logicalHeight).mNonDecorInsets;
}
- private int getNonDecorDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) {
- return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight,
- di.rotation, cutout).width();
- }
-
- private int getNonDecorDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) {
- return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight,
- di.rotation, cutout).height();
- }
-
- private int getConfigDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) {
- return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
- di.rotation, cutout).x;
- }
-
- private int getConfigDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) {
- return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
- di.rotation, cutout).y;
- }
-
- private static Pair<DisplayInfo, WmDisplayCutout> displayInfoForRotation(int rotation,
- boolean withDisplayCutout) {
- return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false);
+ private DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
+ return displayInfoAndCutoutForRotation(
+ rotation, withDisplayCutout, false /* isLongEdgeCutout */);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 34575ae..6951fef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -41,7 +41,6 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.util.Pair;
import android.view.DisplayInfo;
import android.view.InsetsFrameProvider;
import android.view.InsetsState;
@@ -50,8 +49,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.wm.utils.WmDisplayCutout;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -123,7 +120,7 @@
private void updateDisplayFrames() {
mFrames = createDisplayFrames(
mDisplayContent.getInsetsStateController().getRawInsetsState());
- mDisplayBounds.set(0, 0, mFrames.mDisplayWidth, mFrames.mDisplayHeight);
+ mDisplayBounds.set(0, 0, mFrames.mWidth, mFrames.mHeight);
mDisplayContent.mDisplayFrames = mFrames;
mDisplayContent.setBounds(mDisplayBounds);
mDisplayPolicy.layoutWindowLw(mNavBarWindow, null, mFrames);
@@ -131,13 +128,13 @@
}
private DisplayFrames createDisplayFrames(InsetsState insetsState) {
- final Pair<DisplayInfo, WmDisplayCutout> info = displayInfoAndCutoutForRotation(mRotation,
+ final DisplayInfo info = displayInfoAndCutoutForRotation(mRotation,
mHasDisplayCutout, mIsLongEdgeDisplayCutout);
final RoundedCorners roundedCorners = mHasRoundedCorners
? mDisplayContent.calculateRoundedCornersForRotation(mRotation)
: RoundedCorners.NO_ROUNDED_CORNERS;
- return new DisplayFrames(mDisplayContent.getDisplayId(),
- insetsState, info.first, info.second, roundedCorners, new PrivacyIndicatorBounds());
+ return new DisplayFrames(insetsState, info,
+ info.displayCutout, roundedCorners, new PrivacyIndicatorBounds());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 8f2e9b4..e00296f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
@@ -35,13 +36,12 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static com.android.server.wm.utils.WmDisplayCutout.NO_CUTOUT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -286,10 +286,18 @@
DisplayPolicy.isOverlappingWithNavBar(targetWin));
}
- private WindowState createNavigationBarWindow() {
- final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar");
- win.mHasSurface = true;
- return win;
+ @Test
+ public void testUpdateDisplayConfigurationByDecor() {
+ final WindowState navbar = createNavBarWithProvidedInsets(mDisplayContent);
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ final DisplayInfo di = mDisplayContent.getDisplayInfo();
+ final int prevScreenHeightDp = mDisplayContent.getConfiguration().screenHeightDp;
+ assertTrue(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
+ assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
+ di.logicalWidth, di.logicalHeight).mConfigInsets.bottom);
+ mDisplayContent.sendNewConfiguration();
+ assertNotEquals(prevScreenHeightDp, mDisplayContent.getConfiguration().screenHeightDp);
+ assertFalse(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
}
@UseTestDisplay(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
@@ -307,7 +315,7 @@
mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
mImeWindow.mAboveInsetsState.set(state);
- mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
+ mDisplayContent.mDisplayFrames = new DisplayFrames(
state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 97b1c91..fe890d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -30,25 +30,14 @@
import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyInt;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Binder;
-import android.os.IBinder;
-import android.testing.TestableResources;
-import android.util.Pair;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.WindowManagerGlobal;
-import com.android.internal.R;
-import com.android.server.wm.utils.WmDisplayCutout;
-
import org.junit.Before;
public class DisplayPolicyTestsBase extends WindowTestsBase {
@@ -69,23 +58,8 @@
mDisplayPolicy = mDisplayContent.getDisplayPolicy();
spyOn(mDisplayPolicy);
-
- final TestContextWrapper context = new TestContextWrapper(
- mDisplayPolicy.getContext(), mDisplayPolicy.getCurrentUserResources());
- final TestableResources resources = context.getResourceMocker();
- resources.addOverride(R.dimen.navigation_bar_height, NAV_BAR_HEIGHT);
- resources.addOverride(R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT);
- resources.addOverride(R.dimen.navigation_bar_width, NAV_BAR_HEIGHT);
- resources.addOverride(R.dimen.navigation_bar_frame_height_landscape, NAV_BAR_HEIGHT);
- resources.addOverride(R.dimen.navigation_bar_frame_height, NAV_BAR_HEIGHT);
- doReturn(STATUS_BAR_HEIGHT).when(mDisplayPolicy).getStatusBarHeightForRotation(anyInt());
- doReturn(resources.getResources()).when(mDisplayPolicy).getCurrentUserResources();
doReturn(true).when(mDisplayPolicy).hasNavigationBar();
doReturn(true).when(mDisplayPolicy).hasStatusBar();
-
- mDisplayContent.getDisplayRotation().configure(DISPLAY_WIDTH, DISPLAY_HEIGHT);
- mDisplayPolicy.onConfigurationChanged();
-
addWindow(mStatusBarWindow);
addWindow(mNavBarWindow);
@@ -101,24 +75,20 @@
win.mHasSurface = true;
}
- static Pair<DisplayInfo, WmDisplayCutout> displayInfoAndCutoutForRotation(int rotation,
- boolean withDisplayCutout, boolean isLongEdgeCutout) {
- final DisplayInfo info = new DisplayInfo();
- WmDisplayCutout cutout = WmDisplayCutout.NO_CUTOUT;
-
+ DisplayInfo displayInfoAndCutoutForRotation(int rotation, boolean withDisplayCutout,
+ boolean isLongEdgeCutout) {
+ final DisplayInfo info = mDisplayContent.getDisplayInfo();
final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
info.rotation = rotation;
- if (withDisplayCutout) {
- cutout = WmDisplayCutout.computeSafeInsets(
- displayCutoutForRotation(rotation, isLongEdgeCutout), info.logicalWidth,
- info.logicalHeight);
- info.displayCutout = cutout.getDisplayCutout();
- } else {
- info.displayCutout = null;
- }
- return Pair.create(info, cutout);
+ mDisplayContent.mInitialDisplayCutout = withDisplayCutout
+ ? displayCutoutForRotation(ROTATION_0, isLongEdgeCutout)
+ : DisplayCutout.NO_CUTOUT;
+ info.displayCutout = mDisplayContent.calculateDisplayCutoutForRotation(rotation);
+ mDisplayContent.updateBaseDisplayMetrics(DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ info.logicalDensityDpi, info.physicalXDpi, info.physicalYDpi);
+ return info;
}
private static DisplayCutout displayCutoutForRotation(int rotation, boolean isLongEdgeCutout) {
@@ -152,33 +122,4 @@
return DisplayCutout.fromBoundingRect((int) rectF.left, (int) rectF.top,
(int) rectF.right, (int) rectF.bottom, pos);
}
-
- static class TestContextWrapper extends ContextWrapper {
- private final TestableResources mResourceMocker;
-
- TestContextWrapper(Context targetContext, Resources targetResources) {
- super(targetContext);
- mResourceMocker = new TestableResources(targetResources);
- }
-
- @Override
- public int checkPermission(String permission, int pid, int uid) {
- return PackageManager.PERMISSION_GRANTED;
- }
-
- @Override
- public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
- return PackageManager.PERMISSION_GRANTED;
- }
-
- @Override
- public Resources getResources() {
- return mResourceMocker.getResources();
- }
-
- TestableResources getResourceMocker() {
- return mResourceMocker;
- }
- }
-
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 892b5f9..89f7111 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -20,6 +20,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
@@ -68,7 +69,6 @@
import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.After;
import org.junit.AfterClass;
@@ -1008,7 +1008,7 @@
mMockDisplayContent = mock(DisplayContent.class);
mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay;
when(mMockDisplayContent.calculateDisplayCutoutForRotation(anyInt()))
- .thenReturn(WmDisplayCutout.NO_CUTOUT);
+ .thenReturn(NO_CUTOUT);
when(mMockDisplayContent.getDefaultTaskDisplayArea())
.thenReturn(mock(TaskDisplayArea.class));
when(mMockDisplayContent.getWindowConfiguration())
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index f6ffa91..2f1cc20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3133,8 +3133,8 @@
}
private static void resizeDisplay(DisplayContent displayContent, int width, int height) {
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
+ displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
+ displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
final Configuration c = new Configuration();
displayContent.computeScreenConfiguration(c);
displayContent.onRequestedOverrideConfigurationChanged(c);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index f4323db..f322779 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -36,7 +36,6 @@
import static android.view.Surface.ROTATION_90;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -692,12 +691,9 @@
// Setup the display with a top stable inset. The later assertion will ensure the inset is
// excluded from screenHeightDp.
final int statusBarHeight = 100;
- final DisplayPolicy policy = display.getDisplayPolicy();
- doAnswer(invocationOnMock -> {
- final Rect insets = invocationOnMock.<Rect>getArgument(0);
- insets.top = statusBarHeight;
- return null;
- }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0));
+ final DisplayInfo di = display.getDisplayInfo();
+ display.getDisplayPolicy().getDecorInsetsInfo(di.rotation,
+ di.logicalWidth, di.logicalHeight).mConfigInsets.top = statusBarHeight;
// Without limiting to be inside the parent bounds, the out screen size should keep relative
// to the input bounds.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index f5304d0..aa3ca18 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -18,13 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -33,7 +26,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import android.annotation.Nullable;
@@ -47,7 +39,6 @@
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
-import android.view.WindowInsets;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
@@ -209,26 +200,6 @@
doReturn(true).when(newDisplay).supportsSystemDecorations();
doReturn(true).when(displayPolicy).hasNavigationBar();
doReturn(NAV_BAR_BOTTOM).when(displayPolicy).navigationBarPosition(anyInt());
- doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsets(any(),
- eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()));
- doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsets(any(),
- eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()
- | WindowInsets.Type.statusBars()));
- final int[] nonDecorTypes = new int[]{
- ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT,
- ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR
- };
- doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes(
- any(),
- eq(nonDecorTypes));
- final int[] stableTypes = new int[]{
- ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT,
- ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT,
- ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR
- };
- doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes(
- any(),
- eq(stableTypes));
} else {
doReturn(false).when(displayPolicy).hasNavigationBar();
doReturn(false).when(displayPolicy).hasStatusBar();
@@ -241,11 +212,6 @@
displayPolicy.finishScreenTurningOn();
if (mStatusBarHeight > 0) {
doReturn(true).when(displayPolicy).hasStatusBar();
- doAnswer(invocation -> {
- Rect inOutInsets = (Rect) invocation.getArgument(0);
- inOutInsets.top = mStatusBarHeight;
- return null;
- }).when(displayPolicy).convertNonDecorInsetsToStableInsets(any(), anyInt());
}
Configuration c = new Configuration();
newDisplay.computeScreenConfiguration(c);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 8288713..6333508 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -52,6 +52,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.InsetsState;
@@ -63,8 +64,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.wm.utils.WmDisplayCutout;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -164,8 +163,8 @@
// Apply the fixed transform
Configuration config = new Configuration();
final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0);
- final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
- final DisplayFrames displayFrames = new DisplayFrames(dc.getDisplayId(), new InsetsState(),
+ final DisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
+ final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(),
info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 77e12f4..b6373b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -26,6 +26,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.Process.SYSTEM_UID;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
@@ -69,6 +70,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
@@ -84,6 +86,7 @@
import android.view.Gravity;
import android.view.IDisplayWindowInsetsController;
import android.view.IWindow;
+import android.view.InsetsFrameProvider;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsVisibilities;
@@ -417,6 +420,15 @@
true /* ownerCanManageAppTokens */);
}
+ WindowState createNavBarWithProvidedInsets(DisplayContent dc) {
+ final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, dc, "navbar");
+ navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, NAV_BAR_HEIGHT))
+ };
+ dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+ return navbar;
+ }
+
WindowState createAppWindow(Task task, int type, String name) {
final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent());
task.addChild(activity, 0);
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index e882b25..418f518 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -987,8 +987,17 @@
/** The ePDG does not support un-authenticated IMSI based emergency PDN bringup **/
public static final int IWLAN_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED = 0x2B2F;
- // Device is unable to establish an IPSec tunnel with the ePDG for any reason
- // e.g authentication fail or certificate validation or DNS Resolution and timeout failure.
+ // The below error causes are relevant when the device is unable to establish an IPSec tunnel
+ // with the ePDG for any reason, e.g. authentication fail or certificate validation or DNS
+ // Resolution and timeout failure.
+
+ /**
+ * The requested service was rejected because of congestion in the network while accessing the
+ * IWLAN ePDG. Defined in 3GPP TS 24.502, Section 9.2.4.2.
+ *
+ * @hide
+ */
+ public static final int IWLAN_CONGESTION = 0x3C8C;
/** IKE configuration error resulting in failure */
public static final int IWLAN_IKEV2_CONFIG_FAILURE = 0x4000;
diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp
index 78660c0..fffe33a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/Android.bp
+++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp
@@ -24,6 +24,20 @@
android_test {
name: "FlickerTestApp",
srcs: ["**/*.java"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.appcompat_appcompat",
+ "androidx-constraintlayout_constraintlayout",
+ "androidx.core_core",
+ "androidx.fragment_fragment",
+ "androidx.recyclerview_recyclerview",
+ "androidx.navigation_navigation-common-ktx",
+ "androidx.navigation_navigation-fragment-ktx",
+ "androidx.navigation_navigation-runtime-ktx",
+ "androidx.navigation_navigation-ui-ktx",
+ "kotlin-stdlib",
+ "kotlinx-coroutines-android",
+ ],
sdk_version: "current",
test_suites: ["device-tests"],
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 3e2130d..25216a3 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -163,5 +163,15 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".MailActivity"
+ android:exported="true"
+ android:label="MailApp"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
+ android:theme="@style/Theme.AppCompat.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml b/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml
new file mode 100644
index 0000000..c0165e5
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:shareInterpolator="false">
+ <translate
+ android:duration="500"
+ android:fromYDelta="100%p"
+ android:toYDelta="0%p" />
+</set>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml
new file mode 100644
index 0000000..8a97433
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <fragment
+ android:id="@+id/main_fragment"
+ android:name="androidx.navigation.fragment.NavHostFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:defaultNavHost="true"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:navGraph="@navigation/mail_navigation"></fragment>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml
new file mode 100644
index 0000000..fbbdc5b
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml
new file mode 100644
index 0000000..0e30745
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/mail_recycle_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml
new file mode 100644
index 0000000..c39bfb3
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/list_item_height"
+ android:layout_marginLeft="@dimen/margin_medium"
+ android:layout_marginRight="@dimen/margin_medium">
+ <ImageView
+ android:id="@+id/mail_row_item_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/padding_small"
+ android:paddingEnd="@dimen/padding_small"
+ android:paddingStart="@dimen/padding_small"
+ android:paddingTop="@dimen/padding_small"/>
+ <TextView
+ android:id="@+id/mail_row_item_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingEnd="@dimen/padding_small"
+ android:textSize="@dimen/list_item_text_size" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml b/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml
new file mode 100644
index 0000000..d12707a
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/mail_navigation"
+ app:startDestination="@id/fragment_mail_list">
+ <fragment
+ android:id="@+id/fragment_mail_list"
+ android:name=".MailListFragment"
+ android:label="Mail List">
+ <action
+ android:id="@+id/action_mail_list_to_mail_content"
+ app:destination="@id/fragment_mail_content"
+ app:enterAnim="@anim/slide_in_from_bottom" />
+ </fragment>
+ <fragment
+ android:id="@+id/fragment_mail_content"
+ android:name=".MailContentFragment"
+ android:label="Mail Content">
+ </fragment>
+</navigation>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml
new file mode 100644
index 0000000..0b0763d
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="padding_small">8dp</dimen>
+ <dimen name="margin_medium">16dp</dimen>
+ <dimen name="list_item_height">72dp</dimen>
+ <dimen name="list_item_text_size">24dp</dimen>
+ <dimen name="icon_size">64dp</dimen>
+ <dimen name="icon_text_size">36dp</dimen>
+</resources>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java
new file mode 100644
index 0000000..419d438
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MailActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_mail);
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java
new file mode 100644
index 0000000..984263a
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import static androidx.navigation.fragment.NavHostFragment.findNavController;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.shapes.Shape;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.navigation.NavController;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class MailAdapter extends RecyclerView.Adapter<MailAdapter.ViewHolder> {
+
+ private final Fragment mFragment;
+ private final int mSize;
+
+ static class BadgeShape extends OvalShape {
+ Context mContext;
+ String mLabel;
+
+ BadgeShape(Context context, String label) {
+ mContext = context;
+ mLabel = label;
+ }
+
+ @Override
+ public void draw(Canvas canvas, Paint paint) {
+ final Resources resources = mContext.getResources();
+ int textSize = resources.getDimensionPixelSize(R.dimen.icon_text_size);
+ paint.setColor(Color.BLACK);
+ super.draw(canvas, paint);
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(textSize);
+ paint.setTypeface(Typeface.DEFAULT_BOLD);
+ paint.setTextAlign(Paint.Align.CENTER);
+ Paint.FontMetrics fontMetrics = paint.getFontMetrics();
+ float distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
+ canvas.drawText(
+ mLabel,
+ rect().centerX(),
+ rect().centerY() + distance,
+ paint);
+ }
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+
+ NavController mNavController;
+ ImageView mImageView;
+ TextView mTextView;
+ DisplayMetrics mDisplayMetrics;
+
+ ViewHolder(@NonNull View itemView, NavController navController) {
+ super(itemView);
+ mNavController = navController;
+ itemView.setOnClickListener(this::onClick);
+ mImageView = itemView.findViewById(R.id.mail_row_item_icon);
+ mTextView = itemView.findViewById(R.id.mail_row_item_text);
+ mDisplayMetrics = itemView.getContext().getResources().getDisplayMetrics();
+ }
+
+ void onClick(View v) {
+ mNavController.navigate(R.id.action_mail_list_to_mail_content);
+ }
+
+ public void setContent(int i) {
+ final Resources resources = mImageView.getContext().getResources();
+ final int badgeSize = resources.getDimensionPixelSize(R.dimen.icon_size);
+ final char c = (char) ('A' + i % 26);
+ final Shape badge = new BadgeShape(mImageView.getContext(), String.valueOf(c));
+ ShapeDrawable drawable = new ShapeDrawable();
+ drawable.setIntrinsicHeight(badgeSize);
+ drawable.setIntrinsicWidth(badgeSize);
+ drawable.setShape(badge);
+ mImageView.setImageDrawable(drawable);
+ mTextView.setText(String.format("%s-%04d", c, i));
+ }
+ }
+
+ public MailAdapter(Fragment fragment, int size) {
+ super();
+ mFragment = fragment;
+ mSize = size;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+ View v = LayoutInflater
+ .from(viewGroup.getContext())
+ .inflate(R.layout.mail_row_item, viewGroup, false);
+ return new ViewHolder(v, findNavController(mFragment));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
+ viewHolder.setContent(i);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSize;
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java
new file mode 100644
index 0000000..278b92e
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+public class MailContentFragment extends Fragment {
+ public MailContentFragment() {
+ super(R.layout.fragment_mail_content);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ view.setBackgroundColor(Color.LTGRAY);
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java
new file mode 100644
index 0000000..551820c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class MailListFragment extends Fragment {
+
+ protected RecyclerView mRecyclerView;
+ protected RecyclerView.LayoutManager mLayoutManager;
+ protected MailAdapter mAdapter;
+
+ public MailListFragment() {
+ super(R.layout.fragment_mail_list);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_mail_list, container, false);
+ mRecyclerView = rootView.findViewById(R.id.mail_recycle_view);
+ mAdapter = new MailAdapter(this, 1000);
+ mRecyclerView.setAdapter(mAdapter);
+ mLayoutManager = new LinearLayoutManager(getActivity());
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ return rootView;
+ }
+}