diff options
290 files changed, 5427 insertions, 3528 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 0de0a1cf9c8e..e8bcfd2f5bb8 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -112,6 +112,7 @@ import android.text.TextUtils; import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.EventLog; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.LongArrayQueue; @@ -334,12 +335,18 @@ public class AlarmManagerService extends SystemService { "REORDER_ALARMS_FOR_TARE", }); - BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic(); - BroadcastOptions mOptsWithFgsForAlarmClock = BroadcastOptions.makeBasic(); - BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic(); - BroadcastOptions mOptsTimeBroadcast = BroadcastOptions.makeBasic(); + BroadcastOptions mOptsWithFgs = makeBasicAlarmBroadcastOptions(); + BroadcastOptions mOptsWithFgsForAlarmClock = makeBasicAlarmBroadcastOptions(); + BroadcastOptions mOptsWithoutFgs = makeBasicAlarmBroadcastOptions(); + BroadcastOptions mOptsTimeBroadcast = makeBasicAlarmBroadcastOptions(); ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic(); - BroadcastOptions mBroadcastOptsRestrictBal = BroadcastOptions.makeBasic(); + BroadcastOptions mBroadcastOptsRestrictBal = makeBasicAlarmBroadcastOptions(); + + private static BroadcastOptions makeBasicAlarmBroadcastOptions() { + final BroadcastOptions b = BroadcastOptions.makeBasic(); + b.setAlarmBroadcast(true); + return b; + } // TODO(b/172085676): Move inside alarm store. private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser = @@ -2299,7 +2306,11 @@ public class AlarmManagerService extends SystemService { + " reached for uid: " + UserHandle.formatUid(callingUid) + ", callingPackage: " + callingPackage; Slog.w(TAG, errorMsg); - throw new IllegalStateException(errorMsg); + if (callingUid != Process.SYSTEM_UID) { + throw new IllegalStateException(errorMsg); + } else { + EventLog.writeEvent(0x534e4554, "234441463", -1, errorMsg); + } } setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ec4ad8b704c3..0126199add0c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5956,7 +5956,7 @@ package android.location { method public int getEphemerisSource(); method @FloatRange public double getIonoDelayMeters(); method @IntRange(from=0, to=1023) public int getIssueOfDataClock(); - method @IntRange(from=0, to=255) public int getIssueOfDataEphemeris(); + method @IntRange(from=0, to=1023) public int getIssueOfDataEphemeris(); method @Nullable public android.location.SatellitePvt.PositionEcef getPositionEcef(); method @IntRange(from=0) public long getTimeOfClockSeconds(); method @IntRange(from=0) public long getTimeOfEphemerisSeconds(); @@ -5984,7 +5984,7 @@ package android.location { method @NonNull public android.location.SatellitePvt.Builder setEphemerisSource(int); method @NonNull public android.location.SatellitePvt.Builder setIonoDelayMeters(@FloatRange(from=0.0f, to=100.0f) double); method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataClock(@IntRange(from=0, to=1023) int); - method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataEphemeris(@IntRange(from=0, to=255) int); + method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataEphemeris(@IntRange(from=0, to=1023) int); method @NonNull public android.location.SatellitePvt.Builder setPositionEcef(@NonNull android.location.SatellitePvt.PositionEcef); method @NonNull public android.location.SatellitePvt.Builder setTimeOfClockSeconds(@IntRange(from=0) long); method @NonNull public android.location.SatellitePvt.Builder setTimeOfEphemerisSeconds(@IntRange(from=0) long); diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index 1d3b5e2aa4ee..482f456b5d83 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -227,6 +227,18 @@ public class ActivityClient { } /** + * Returns the windowing mode of the task that hosts the activity, or {@code -1} if task is not + * found. + */ + public int getTaskWindowingMode(IBinder activityToken) { + try { + return getActivityClientController().getTaskWindowingMode(activityToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the non-finishing activity token below in the same task if it belongs to the same * process. */ diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java index 877e7d3b3bf7..57dacd024ba1 100644 --- a/core/java/android/app/ActivityTransitionState.java +++ b/core/java/android/app/ActivityTransitionState.java @@ -263,6 +263,11 @@ class ActivityTransitionState { // After orientation change, the onResume can come in before the top Activity has // left, so if the Activity is not top, wait a second for the top Activity to exit. if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) { + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> { + mEnterTransitionCoordinator = null; + }); + } restoreExitedViews(); restoreReenteringViews(); } else { @@ -271,6 +276,11 @@ class ActivityTransitionState { public void run() { if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isWaitingForRemoteExit()) { + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> { + mEnterTransitionCoordinator = null; + }); + } restoreExitedViews(); restoreReenteringViews(); } else if (mEnterTransitionCoordinator.isReturning()) { diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 56f8760f6059..f0e14483d98a 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -53,6 +53,7 @@ public class BroadcastOptions extends ComponentOptions { private String[] mRequireNoneOfPermissions; private long mRequireCompatChangeId = CHANGE_INVALID; private boolean mRequireCompatChangeEnabled = true; + private boolean mIsAlarmBroadcast = false; private long mIdForResponseEvent; /** @@ -149,6 +150,13 @@ public class BroadcastOptions extends ComponentOptions { "android:broadcast.requireCompatChangeEnabled"; /** + * Corresponds to {@link #setAlarmBroadcast(boolean)} + * @hide + */ + public static final String KEY_ALARM_BROADCAST = + "android:broadcast.is_alarm"; + + /** * @hide * @deprecated Use {@link android.os.PowerExemptionManager# * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead. @@ -207,6 +215,7 @@ public class BroadcastOptions extends ComponentOptions { mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID); mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true); mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT); + mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false); } /** @@ -498,6 +507,27 @@ public class BroadcastOptions extends ComponentOptions { mRequireCompatChangeEnabled = true; } + /** + * When set, this broadcast will be understood as having originated from an + * alarm going off. Only the OS itself can use this option; uses by other + * senders will be ignored. + * @hide + * + * @param senderIsAlarm Whether the broadcast is alarm-triggered. + */ + public void setAlarmBroadcast(boolean senderIsAlarm) { + mIsAlarmBroadcast = senderIsAlarm; + } + + /** + * Did this broadcast originate from an alarm triggering? + * @return true if this broadcast is an alarm message, false otherwise + * @hide + */ + public boolean isAlarmBroadcast() { + return mIsAlarmBroadcast; + } + /** {@hide} */ public long getRequireCompatChangeId() { return mRequireCompatChangeId; @@ -560,6 +590,9 @@ public class BroadcastOptions extends ComponentOptions { b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, mTemporaryAppAllowlistReasonCode); b.putString(KEY_TEMPORARY_APP_ALLOWLIST_REASON, mTemporaryAppAllowlistReason); } + if (mIsAlarmBroadcast) { + b.putBoolean(KEY_ALARM_BROADCAST, true); + } if (mMinManifestReceiverApiLevel != 0) { b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel); } diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 7aeb2b26dd15..f5e5cda9c639 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -78,6 +78,7 @@ interface IActivityClientController { boolean willActivityBeVisible(in IBinder token); int getDisplayId(in IBinder activityToken); int getTaskForActivity(in IBinder token, in boolean onlyRoot); + int getTaskWindowingMode(in IBinder activityToken); IBinder getActivityTokenBelow(IBinder token); ComponentName getCallingActivity(in IBinder token); String getCallingPackage(in IBinder token); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 8984c4292023..556058b567f9 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -783,6 +783,17 @@ public class Instrumentation { return null; } + /** + * This is called after starting an Activity and provides the result code that defined in + * {@link ActivityManager}, like {@link ActivityManager#START_SUCCESS}. + * + * @param result the result code that returns after starting an Activity. + * @param bOptions the bundle generated from {@link ActivityOptions} that originally + * being used to start the Activity. + * @hide + */ + public void onStartActivityResult(int result, @NonNull Bundle bOptions) {} + final boolean match(Context who, Activity activity, Intent intent) { @@ -1344,6 +1355,28 @@ public class Instrumentation { return apk.getAppFactory(); } + /** + * This should be called before {@link #checkStartActivityResult(int, Object)}, because + * exceptions might be thrown while checking the results. + */ + private void notifyStartActivityResult(int result, @Nullable Bundle options) { + if (mActivityMonitors == null) { + return; + } + synchronized (mSync) { + final int size = mActivityMonitors.size(); + for (int i = 0; i < size; i++) { + final ActivityMonitor am = mActivityMonitors.get(i); + if (am.ignoreMatchingSpecificIntents()) { + if (options == null) { + options = ActivityOptions.makeBasic().toBundle(); + } + am.onStartActivityResult(result, options); + } + } + } + } + private void prePerformCreate(Activity activity) { if (mWaitingActivities != null) { synchronized (mSync) { @@ -1802,6 +1835,7 @@ public class Instrumentation { who.getOpPackageName(), who.getAttributionTag(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); + notifyStartActivityResult(result, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); @@ -1876,6 +1910,7 @@ public class Instrumentation { int result = ActivityTaskManager.getService().startActivities(whoThread, who.getOpPackageName(), who.getAttributionTag(), intents, resolvedTypes, token, options, userId); + notifyStartActivityResult(result, options); checkStartActivityResult(result, intents[0]); return result; } catch (RemoteException e) { @@ -1947,6 +1982,7 @@ public class Instrumentation { who.getOpPackageName(), who.getAttributionTag(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target, requestCode, 0, null, options); + notifyStartActivityResult(result, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); @@ -2017,6 +2053,7 @@ public class Instrumentation { who.getOpPackageName(), who.getAttributionTag(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho, requestCode, 0, null, options, user.getIdentifier()); + notifyStartActivityResult(result, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); @@ -2068,6 +2105,7 @@ public class Instrumentation { token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options, ignoreTargetSecurity, userId); + notifyStartActivityResult(result, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); @@ -2115,6 +2153,7 @@ public class Instrumentation { int result = appTask.startActivity(whoThread.asBinder(), who.getOpPackageName(), who.getAttributionTag(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), options); + notifyStartActivityResult(result, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index dac55ae08448..8db298ff8a85 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1325,7 +1325,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * flashlight brightness level via * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }. * If this value is equal to 1, flashlight brightness control is not supported. - * The value for this key will be null for devices with no flash unit.</p> + * The value for this key will be null for devices with no flash unit. + * This level must be set to a safe value to prevent any burn out issues.</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> */ @PublicKey diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java index 6cf5d60acec8..cbd806617a69 100644 --- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java +++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java @@ -45,46 +45,41 @@ public class MarshalQueryableArray<T> implements MarshalQueryable<T> { private static final boolean DEBUG = false; private static interface PrimitiveArrayFiller { - public void fillPosition(Object arr, int index, ByteBuffer buffer); + public void fillArray(Object arr, int size, ByteBuffer buffer); static PrimitiveArrayFiller getPrimitiveArrayFiller(Class<?> componentType) { if (componentType == int.class) { return new PrimitiveArrayFiller() { @Override - public void fillPosition(Object arr, int index, ByteBuffer buffer) { - int i = buffer.getInt(); - Array.setInt(arr, index, i); + public void fillArray(Object arr, int size, ByteBuffer buffer) { + buffer.asIntBuffer().get(int[].class.cast(arr), 0, size); } }; } else if (componentType == float.class) { return new PrimitiveArrayFiller() { @Override - public void fillPosition(Object arr, int index, ByteBuffer buffer) { - float i = buffer.getFloat(); - Array.setFloat(arr, index, i); + public void fillArray(Object arr, int size, ByteBuffer buffer) { + buffer.asFloatBuffer().get(float[].class.cast(arr), 0, size); } }; } else if (componentType == long.class) { return new PrimitiveArrayFiller() { @Override - public void fillPosition(Object arr, int index, ByteBuffer buffer) { - long i = buffer.getLong(); - Array.setLong(arr, index, i); + public void fillArray(Object arr, int size, ByteBuffer buffer) { + buffer.asLongBuffer().get(long[].class.cast(arr), 0, size); } }; } else if (componentType == double.class) { return new PrimitiveArrayFiller() { @Override - public void fillPosition(Object arr, int index, ByteBuffer buffer) { - double i = buffer.getDouble(); - Array.setDouble(arr, index, i); + public void fillArray(Object arr, int size, ByteBuffer buffer) { + buffer.asDoubleBuffer().get(double[].class.cast(arr), 0, size); } }; } else if (componentType == byte.class) { return new PrimitiveArrayFiller() { @Override - public void fillPosition(Object arr, int index, ByteBuffer buffer) { - byte i = buffer.get(); - Array.setByte(arr, index, i); + public void fillArray(Object arr, int size, ByteBuffer buffer) { + buffer.get(byte[].class.cast(arr), 0, size); } }; } @@ -93,13 +88,6 @@ public class MarshalQueryableArray<T> implements MarshalQueryable<T> { } }; - static void unmarshalPrimitiveArray(Object arr, int size, ByteBuffer buffer, - PrimitiveArrayFiller filler) { - for (int i = 0; i < size; i++) { - filler.fillPosition(arr, i, buffer); - } - } - private class MarshalerArray extends Marshaler<T> { private final Class<T> mClass; private final Marshaler<?> mComponentMarshaler; @@ -150,8 +138,8 @@ public class MarshalQueryableArray<T> implements MarshalQueryable<T> { array = Array.newInstance(mComponentClass, arraySize); if (isUnwrappedPrimitiveClass(mComponentClass) && mComponentClass == getPrimitiveTypeClass(mNativeType)) { - unmarshalPrimitiveArray(array, arraySize, buffer, - PrimitiveArrayFiller.getPrimitiveArrayFiller(mComponentClass)); + PrimitiveArrayFiller.getPrimitiveArrayFiller(mComponentClass).fillArray(array, + arraySize, buffer); } else { for (int i = 0; i < arraySize; ++i) { Object elem = mComponentMarshaler.unmarshal(buffer); diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java index e8e4fc988937..f2525d17e30a 100644 --- a/core/java/android/hardware/radio/ProgramList.java +++ b/core/java/android/hardware/radio/ProgramList.java @@ -22,10 +22,11 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -41,7 +42,7 @@ public final class ProgramList implements AutoCloseable { private final Object mLock = new Object(); private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms = - new HashMap<>(); + new ArrayMap<>(); private final List<ListCallback> mListCallbacks = new ArrayList<>(); private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>(); @@ -184,8 +185,14 @@ public final class ProgramList implements AutoCloseable { listCallbacksCopied = new ArrayList<>(mListCallbacks); if (chunk.isPurge()) { - for (ProgramSelector.Identifier id : mPrograms.keySet()) { - removeLocked(id, removedList); + Iterator<Map.Entry<ProgramSelector.Identifier, RadioManager.ProgramInfo>> + programsIterator = mPrograms.entrySet().iterator(); + while (programsIterator.hasNext()) { + RadioManager.ProgramInfo removed = programsIterator.next().getValue(); + if (removed != null) { + removedList.add(removed.getSelector().getPrimaryId()); + } + programsIterator.remove(); } } diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl index f4a0dfaa4432..1940042a1052 100644 --- a/core/java/android/view/IDisplayWindowInsetsController.aidl +++ b/core/java/android/view/IDisplayWindowInsetsController.aidl @@ -16,6 +16,7 @@ package android.view; +import android.content.ComponentName; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.InsetsVisibilities; @@ -30,10 +31,11 @@ oneway interface IDisplayWindowInsetsController { /** * Called when top focused window changes to determine whether or not to take over insets * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false. - * @param packageName: Passes the top package name + * @param component: Passes the top application component in the focused window. * @param requestedVisibilities The insets visibilities requested by the focussed window. */ - void topFocusedWindowChanged(String packageName, in InsetsVisibilities insetsVisibilities); + void topFocusedWindowChanged(in ComponentName component, + in InsetsVisibilities insetsVisibilities); /** * @see IWindow#insetsChanged diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl index 61f524f51786..c4d307073d12 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -80,11 +80,6 @@ interface IRecentsAnimationController { void setAnimationTargetsBehindSystemBars(boolean behindSystemBars); /** - * Hides the current input method if one is showing. - */ - void hideCurrentInputMethod(); - - /** * Clean up the screenshot of previous task which was created during recents animation that * was cancelled by a stack order change. * diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java index 5ed9d2f90a72..57a0330e3c18 100644 --- a/core/java/android/view/WindowLayout.java +++ b/core/java/android/view/WindowLayout.java @@ -118,11 +118,11 @@ public class WindowLayout { } if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) { if (displayFrame.width() < displayFrame.height()) { - displayCutoutSafeExceptMaybeBars.top = MIN_Y; - displayCutoutSafeExceptMaybeBars.bottom = MAX_Y; + displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE; + displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; } else { - displayCutoutSafeExceptMaybeBars.left = MIN_X; - displayCutoutSafeExceptMaybeBars.right = MAX_X; + displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE; + displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE; } } final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0; @@ -132,23 +132,23 @@ public class WindowLayout { final Insets systemBarsInsets = state.calculateInsets( displayFrame, WindowInsets.Type.systemBars(), requestedVisibilities); if (systemBarsInsets.left > 0) { - displayCutoutSafeExceptMaybeBars.left = MIN_X; + displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE; } if (systemBarsInsets.top > 0) { - displayCutoutSafeExceptMaybeBars.top = MIN_Y; + displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE; } if (systemBarsInsets.right > 0) { - displayCutoutSafeExceptMaybeBars.right = MAX_X; + displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE; } if (systemBarsInsets.bottom > 0) { - displayCutoutSafeExceptMaybeBars.bottom = MAX_Y; + displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; } } if (type == TYPE_INPUT_METHOD) { final InsetsSource navSource = state.peekSource(ITYPE_NAVIGATION_BAR); if (navSource != null && navSource.calculateInsets(displayFrame, true).bottom > 0) { // The IME can always extend under the bottom cutout if the navbar is there. - displayCutoutSafeExceptMaybeBars.bottom = MAX_Y; + displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; } } final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen; diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index ac4c2ea448ac..d37756551db3 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -404,7 +404,7 @@ public final class WindowManagerGlobal { try { root.setView(view, wparams, panelParentView, userId); } catch (RuntimeException e) { - final int viewIndex = findViewLocked(view, false); + final int viewIndex = (index >= 0) ? index : (mViews.size() - 1); // BadTokenException or InvalidDisplayException, clean up. if (viewIndex >= 0) { removeViewLocked(viewIndex, true); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 23393ffe885c..2268bef2c1d9 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6440,9 +6440,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setText(CharSequence text, BufferType type) { setText(text, type, true, 0); - if (mCharWrapper != null) { - mCharWrapper.mChars = null; - } + // drop any potential mCharWrappper leaks + mCharWrapper = null; } @UnsupportedAppUsage @@ -6653,11 +6652,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * since the TextView has no way to know that the text * has changed and that it needs to invalidate and re-layout. * + * @throws NullPointerException if text is null + * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length + * * @param text char array to be displayed * @param start start index in the char array * @param len length of char count after {@code start} */ - public final void setText(char[] text, int start, int len) { + public final void setText(/* @NonNull */ char[] text, int start, int len) { int oldlen = 0; if (start < 0 || len < 0 || start + len > text.length) { @@ -13888,16 +13890,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { + @NonNull private char[] mChars; private int mStart, mLength; - public CharWrapper(char[] chars, int start, int len) { + CharWrapper(@NonNull char[] chars, int start, int len) { mChars = chars; mStart = start; mLength = len; } - /* package */ void set(char[] chars, int start, int len) { + /* package */ void set(@NonNull char[] chars, int start, int len) { mChars = chars; mStart = start; mLength = len; diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index c81184fb2383..1f3841ad8b58 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -110,8 +110,11 @@ public final class TransitionInfo implements Parcelable { /** The container is an input-method window. */ public static final int FLAG_IS_INPUT_METHOD = 1 << 8; + /** The container is ActivityEmbedding embedded. */ + public static final int FLAG_IS_EMBEDDED = 1 << 9; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 9; + public static final int FLAG_FIRST_CUSTOM = 1 << 10; /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { @@ -125,6 +128,7 @@ public final class TransitionInfo implements Parcelable { FLAG_OCCLUDES_KEYGUARD, FLAG_DISPLAY_HAS_ALERT_WINDOWS, FLAG_IS_INPUT_METHOD, + FLAG_IS_EMBEDDED, FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -325,6 +329,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS"); } + if ((flags & FLAG_IS_EMBEDDED) != 0) { + sb.append((sb.length() == 0 ? "" : "|") + "IS_EMBEDDED"); + } if ((flags & FLAG_FIRST_CUSTOM) != 0) { sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM"); } diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java index 47ce2d87e69f..cc4fbab1f190 100644 --- a/core/tests/coretests/src/android/widget/TextViewTest.java +++ b/core/tests/coretests/src/android/widget/TextViewTest.java @@ -304,6 +304,23 @@ public class TextViewTest { assertFalse(mTextView.isCursorVisible()); } + @Test(expected = NullPointerException.class) + @UiThreadTest + public void setTextCharArrayNullThrows() { + mTextView = new TextView(mActivity); + mTextView.setText((char[]) null, 0, 0); + } + + @Test + @UiThreadTest + public void setTextCharArrayValidAfterSetTextString() { + mTextView = new TextView(mActivity); + mTextView.setText(new char[] { 'h', 'i'}, 0, 2); + CharSequence charWrapper = mTextView.getText(); + mTextView.setText("null out char wrapper"); + assertEquals("hi", charWrapper.toString()); + } + private String createLongText() { int size = 600 * 1000; final StringBuilder builder = new StringBuilder(size); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 2f79caeec7ba..da9fd0c2d96f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -16,6 +16,7 @@ package androidx.window.extensions.embedding; +import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -97,6 +98,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final Handler mHandler; private final Object mLock = new Object(); + private final ActivityStartMonitor mActivityStartMonitor; public SplitController() { final MainThreadExecutor executor = new MainThreadExecutor(); @@ -108,7 +110,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen new LifecycleCallbacks()); // Intercept activity starts to route activities to new containers if necessary. Instrumentation instrumentation = activityThread.getInstrumentation(); - instrumentation.addMonitor(new ActivityStartMonitor()); + mActivityStartMonitor = new ActivityStartMonitor(); + instrumentation.addMonitor(mActivityStartMonitor); } /** Updates the embedding rules applied to future activity launches. */ @@ -1385,6 +1388,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return ActivityThread.currentActivityThread().getActivity(activityToken); } + @VisibleForTesting + ActivityStartMonitor getActivityStartMonitor() { + return mActivityStartMonitor; + } + /** * Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it * after creation because the activity could be reparented. @@ -1536,7 +1544,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * A monitor that intercepts all activity start requests originating in the client process and * can amend them to target a specific task fragment to form a split. */ - private class ActivityStartMonitor extends Instrumentation.ActivityMonitor { + @VisibleForTesting + class ActivityStartMonitor extends Instrumentation.ActivityMonitor { + @VisibleForTesting + Intent mCurrentIntent; @Override public Instrumentation.ActivityResult onStartActivity(@NonNull Context who, @@ -1564,11 +1575,29 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // the dedicated container. options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, launchedInTaskFragment.getTaskFragmentToken()); + mCurrentIntent = intent; } } return super.onStartActivity(who, intent, options); } + + @Override + public void onStartActivityResult(int result, @NonNull Bundle bOptions) { + super.onStartActivityResult(result, bOptions); + if (mCurrentIntent != null && result != START_SUCCESS) { + // Clear the pending appeared intent if the activity was not started successfully. + final IBinder token = bOptions.getBinder( + ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN); + if (token != null) { + final TaskFragmentContainer container = getContainer(token); + if (container != null) { + container.clearPendingAppearedIntentIfNeeded(mCurrentIntent); + } + } + } + mCurrentIntent = null; + } } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index abf32a26efa2..a188e2bf4985 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -198,6 +198,22 @@ class TaskFragmentContainer { return mPendingAppearedIntent; } + void setPendingAppearedIntent(@Nullable Intent intent) { + mPendingAppearedIntent = intent; + } + + /** + * Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the + * pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has + * running activities). + */ + void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) { + if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) { + return; + } + mPendingAppearedIntent = null; + } + boolean hasActivity(@NonNull IBinder token) { if (mInfo != null && mInfo.getActivities().contains(token)) { return true; @@ -230,13 +246,18 @@ class TaskFragmentContainer { void setInfo(@NonNull TaskFragmentInfo info) { if (!mIsFinished && mInfo == null && info.isEmpty()) { - // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if it is - // still empty after timeout. + // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no + // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if + // it is still empty after timeout. mAppearEmptyTimeout = () -> { mAppearEmptyTimeout = null; mController.onTaskFragmentAppearEmptyTimeout(this); }; - mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS); + if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) { + mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS); + } else { + mAppearEmptyTimeout.run(); + } } else if (mAppearEmptyTimeout != null && !info.isEmpty()) { mController.getHandler().removeCallbacks(mAppearEmptyTimeout); mAppearEmptyTimeout = null; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index 18086f552ea3..6bfb16a3c22d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -25,8 +25,7 @@ import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.annotation.Nullable; import android.app.Activity; -import android.app.ActivityManager; -import android.app.ActivityManager.AppTask; +import android.app.ActivityClient; import android.app.Application; import android.app.WindowConfiguration; import android.content.Context; @@ -34,7 +33,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; -import android.util.Log; import androidx.annotation.NonNull; import androidx.window.common.CommonFoldingFeature; @@ -179,57 +177,49 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private List<DisplayFeature> getDisplayFeatures( @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { List<DisplayFeature> features = new ArrayList<>(); - int displayId = activity.getDisplay().getDisplayId(); - if (displayId != DEFAULT_DISPLAY) { - Log.w(TAG, "This sample doesn't support display features on secondary displays"); + if (!shouldReportDisplayFeatures(activity)) { return features; - } else if (isTaskInMultiWindowMode(activity)) { - // It is recommended not to report any display features in multi-window mode, since it - // won't be possible to synchronize the display feature positions with window movement. - return features; - } else { - for (CommonFoldingFeature baseFeature : storedFeatures) { - Integer state = convertToExtensionState(baseFeature.getState()); - if (state == null) { - continue; - } - Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(activity, featureRect); + } - if (!isRectZero(featureRect)) { - // TODO(b/228641877) Remove guarding if when fixed. - features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); - } + int displayId = activity.getDisplay().getDisplayId(); + for (CommonFoldingFeature baseFeature : storedFeatures) { + Integer state = convertToExtensionState(baseFeature.getState()); + if (state == null) { + continue; + } + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + + if (!isRectZero(featureRect)) { + // TODO(b/228641877): Remove guarding when fixed. + features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } - return features; } + return features; } /** - * Checks whether the task associated with the activity is in multi-window. If task info is not - * available it defaults to {@code true}. + * Checks whether display features should be reported for the activity. + * TODO(b/238948678): Support reporting display features in all windowing modes. */ - private boolean isTaskInMultiWindowMode(@NonNull Activity activity) { - final ActivityManager am = activity.getSystemService(ActivityManager.class); - if (am == null) { - return true; - } - - final List<AppTask> appTasks = am.getAppTasks(); - final int taskId = activity.getTaskId(); - AppTask task = null; - for (AppTask t : appTasks) { - if (t.getTaskInfo().taskId == taskId) { - task = t; - break; - } + private boolean shouldReportDisplayFeatures(@NonNull Activity activity) { + int displayId = activity.getDisplay().getDisplayId(); + if (displayId != DEFAULT_DISPLAY) { + // Display features are not supported on secondary displays. + return false; } - if (task == null) { - // The task might be removed on the server already. - return true; + final int taskWindowingMode = ActivityClient.getInstance().getTaskWindowingMode( + activity.getActivityToken()); + if (taskWindowingMode == -1) { + // If we cannot determine the task windowing mode for any reason, it is likely that we + // won't be able to determine its position correctly as well. DisplayFeatures' bounds + // in this case can't be computed correctly, so we should skip. + return false; } - return WindowConfiguration.inMultiWindowMode(task.getTaskInfo().getWindowingMode()); + // It is recommended not to report any display features in multi-window mode, since it + // won't be possible to synchronize the display feature positions with window movement. + return !WindowConfiguration.inMultiWindowMode(taskWindowingMode); } /** diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 042547fd30f2..4bc503369d0e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -16,6 +16,7 @@ package androidx.window.extensions.embedding; +import static android.app.ActivityManager.START_CANCELED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -293,6 +294,26 @@ public class SplitControllerTest { } @Test + public void testOnStartActivityResultError() { + final Intent intent = new Intent(); + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, + intent, taskContainer, mSplitController); + final SplitController.ActivityStartMonitor monitor = + mSplitController.getActivityStartMonitor(); + + container.setPendingAppearedIntent(intent); + final Bundle bundle = new Bundle(); + bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, + container.getTaskFragmentToken()); + monitor.mCurrentIntent = intent; + doReturn(container).when(mSplitController).getContainer(any()); + + monitor.onStartActivityResult(START_CANCELED, bundle); + assertNull(container.getPendingAppearedIntent()); + } + + @Test public void testOnActivityCreated() { mSplitController.onActivityCreated(mActivity); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 28c2773e25cb..44c7e6c611de 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -209,21 +209,21 @@ public class TaskFragmentContainerTest { assertNull(container.mAppearEmptyTimeout); - // Not set if it is not appeared empty. - final TaskFragmentInfo info = mock(TaskFragmentInfo.class); - doReturn(new ArrayList<>()).when(info).getActivities(); - doReturn(false).when(info).isEmpty(); - container.setInfo(info); - - assertNull(container.mAppearEmptyTimeout); - // Set timeout if the first info set is empty. + final TaskFragmentInfo info = mock(TaskFragmentInfo.class); container.mInfo = null; doReturn(true).when(info).isEmpty(); container.setInfo(info); assertNotNull(container.mAppearEmptyTimeout); + // Not set if it is not appeared empty. + doReturn(new ArrayList<>()).when(info).getActivities(); + doReturn(false).when(info).isEmpty(); + container.setInfo(info); + + assertNull(container.mAppearEmptyTimeout); + // Remove timeout after the container becomes non-empty. doReturn(false).when(info).isEmpty(); container.setInfo(info); @@ -232,6 +232,7 @@ public class TaskFragmentContainerTest { // Running the timeout will call into SplitController.onTaskFragmentAppearEmptyTimeout. container.mInfo = null; + container.setPendingAppearedIntent(mIntent); doReturn(true).when(info).isEmpty(); container.setInfo(info); container.mAppearEmptyTimeout.run(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index 5a94a0d6f6a5..6694e441084b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -26,6 +26,7 @@ import android.util.Pair; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; @@ -72,6 +73,7 @@ public class ShellInitImpl { private final Transitions mTransitions; private final StartingWindowController mStartingWindow; private final Optional<RecentTasksController> mRecentTasks; + private final Optional<ActivityEmbeddingController> mActivityEmbeddingOptional; private final InitImpl mImpl = new InitImpl(); // An ordered list of init callbacks to be made once shell is first started @@ -93,6 +95,7 @@ public class ShellInitImpl { Optional<UnfoldTransitionHandler> unfoldTransitionHandler, Optional<FreeformTaskListener<?>> freeformTaskListenerOptional, Optional<RecentTasksController> recentTasks, + Optional<ActivityEmbeddingController> activityEmbeddingOptional, Transitions transitions, StartingWindowController startingWindow, ShellExecutor mainExecutor) { @@ -110,6 +113,7 @@ public class ShellInitImpl { mUnfoldTransitionHandler = unfoldTransitionHandler; mFreeformTaskListenerOptional = freeformTaskListenerOptional; mRecentTasks = recentTasks; + mActivityEmbeddingOptional = activityEmbeddingOptional; mTransitions = transitions; mMainExecutor = mainExecutor; mStartingWindow = startingWindow; @@ -139,6 +143,7 @@ public class ShellInitImpl { if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.register(mShellTaskOrganizer); + mActivityEmbeddingOptional.ifPresent(ActivityEmbeddingController::init); mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init); if (mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) { final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions, 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 e0ad9cb66780..7d7c59eb17da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -500,7 +500,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements || (taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME && taskInfo.isVisible); final boolean focusTaskChanged = (mLastFocusedTaskInfo == null - || mLastFocusedTaskInfo.taskId != taskInfo.taskId) && isFocusedOrHome; + || mLastFocusedTaskInfo.taskId != taskInfo.taskId + || mLastFocusedTaskInfo.getWindowingMode() != taskInfo.getWindowingMode()) + && isFocusedOrHome; if (focusTaskChanged) { for (int i = 0; i < mFocusListeners.size(); i++) { mFocusListeners.valueAt(i).onFocusTaskChanged(taskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java new file mode 100644 index 000000000000..82b0270698e4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -0,0 +1,80 @@ +/* + * 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.wm.shell.activityembedding; + +import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; + +import android.content.Context; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.transition.Transitions; + +/** + * Responsible for handling ActivityEmbedding related transitions. + */ +public class ActivityEmbeddingController implements Transitions.TransitionHandler { + + private final Context mContext; + private final Transitions mTransitions; + + public ActivityEmbeddingController(Context context, Transitions transitions) { + mContext = context; + mTransitions = transitions; + } + + /** Registers to handle transitions. */ + public void init() { + mTransitions.addHandler(this); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // TODO(b/207070762) Handle AE animation as a part of other transitions. + // Only handle the transition if all containers are embedded. + for (TransitionInfo.Change change : info.getChanges()) { + if (!isEmbedded(change)) { + return false; + } + } + + // TODO(b/207070762) Implement AE animation. + startTransaction.apply(); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + return true; + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + return null; + } + + private static boolean isEmbedded(@NonNull TransitionInfo.Change change) { + return (change.getFlags() & FLAG_IS_EMBEDDED) != 0; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index 8c0affb0a432..86f9d5b534f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -29,6 +29,13 @@ import com.android.wm.shell.common.annotations.ExternalThread; public interface BackAnimation { /** + * Returns a binder that can be passed to an external process to update back animations. + */ + default IBackAnimation createExternalInterface() { + return null; + } + + /** * Called when a {@link MotionEvent} is generated by a back gesture. * * @param touchX the X touch position of the {@link MotionEvent}. @@ -47,13 +54,6 @@ public interface BackAnimation { void setTriggerBack(boolean triggerBack); /** - * Returns a binder that can be passed to an external process to update back animations. - */ - default IBackAnimation createExternalInterface() { - return null; - } - - /** * Sets the threshold values that defining edge swipe behavior. * @param triggerThreshold the min threshold to trigger back. * @param progressThreshold the max threshold to keep progressing back animation. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 62aa7fe28728..d7f1292cb717 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -101,6 +101,8 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.PinnedStackListenerForwarder; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; import java.io.PrintWriter; import java.util.ArrayList; @@ -119,7 +121,7 @@ import java.util.function.IntConsumer; * * The controller manages addition, removal, and visible state of bubbles on screen. */ -public class BubbleController { +public class BubbleController implements ConfigurationChangeListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; @@ -155,6 +157,7 @@ public class BubbleController { private final DisplayController mDisplayController; private final TaskViewTransitions mTaskViewTransitions; private final SyncTransactionQueue mSyncQueue; + private final ShellController mShellController; // Used to post to main UI thread private final ShellExecutor mMainExecutor; @@ -224,6 +227,7 @@ public class BubbleController { public BubbleController(Context context, + ShellController shellController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, @@ -246,6 +250,7 @@ public class BubbleController { TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { mContext = context; + mShellController = shellController; mLauncherApps = launcherApps; mBarService = statusBarService == null ? IStatusBarService.Stub.asInterface( @@ -414,6 +419,8 @@ public class BubbleController { // Clear out any persisted bubbles on disk that no longer have a valid user. List<UserInfo> users = mUserManager.getAliveUsers(); mDataRepository.sanitizeBubbles(users); + + mShellController.addConfigurationChangeListener(this); } @VisibleForTesting @@ -798,7 +805,8 @@ public class BubbleController { mSavedBubbleKeysPerUser.remove(userId); } - private void updateForThemeChanges() { + @Override + public void onThemeChanged() { if (mStackView != null) { mStackView.onThemeChanged(); } @@ -818,7 +826,8 @@ public class BubbleController { } } - private void onConfigChanged(Configuration newConfig) { + @Override + public void onConfigurationChanged(Configuration newConfig) { if (mBubblePositioner != null) { mBubblePositioner.update(); } @@ -1678,13 +1687,6 @@ public class BubbleController { } @Override - public void updateForThemeChanges() { - mMainExecutor.execute(() -> { - BubbleController.this.updateForThemeChanges(); - }); - } - - @Override public void expandStackAndSelectBubble(BubbleEntry entry) { mMainExecutor.execute(() -> { BubbleController.this.expandStackAndSelectBubble(entry); @@ -1823,13 +1825,6 @@ public class BubbleController { } @Override - public void onConfigChanged(Configuration newConfig) { - mMainExecutor.execute(() -> { - BubbleController.this.onConfigChanged(newConfig); - }); - } - - @Override public void onNotificationPanelExpandedChanged(boolean expanded) { mMainExecutor.execute( () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 0072da19b9ef..f8ccf2364b4c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -107,9 +107,6 @@ public interface Bubbles { /** Tell the stack of bubbles to collapse. */ void collapseStack(); - /** Tell the controller need update its UI to fit theme. */ - void updateForThemeChanges(); - /** * Request the stack expand if needed, then select the specified Bubble as current. * If no bubble exists for this entry, one is created. @@ -255,13 +252,6 @@ public interface Bubbles { */ void onUserRemoved(int removedUserId); - /** - * Called when config changed. - * - * @param newConfig the new config. - */ - void onConfigChanged(Configuration newConfig); - /** Description of current bubble state. */ void dump(PrintWriter pw, String[] args); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 6a2acf438302..b3f62477077c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; @@ -324,7 +325,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } @Override - public void topFocusedWindowChanged(String packageName, + public void topFocusedWindowChanged(ComponentName component, InsetsVisibilities requestedVisibilities) { // Do nothing } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java index b6705446674a..f546f110e87c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.common; +import android.content.ComponentName; import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; @@ -171,14 +172,14 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } } - private void topFocusedWindowChanged(String packageName, + private void topFocusedWindowChanged(ComponentName component, InsetsVisibilities requestedVisibilities) { CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId); if (listeners == null) { return; } for (OnInsetsChangedListener listener : listeners) { - listener.topFocusedWindowChanged(packageName, requestedVisibilities); + listener.topFocusedWindowChanged(component, requestedVisibilities); } } @@ -186,10 +187,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan private class DisplayWindowInsetsControllerImpl extends IDisplayWindowInsetsController.Stub { @Override - public void topFocusedWindowChanged(String packageName, + public void topFocusedWindowChanged(ComponentName component, InsetsVisibilities requestedVisibilities) throws RemoteException { mMainExecutor.execute(() -> { - PerDisplay.this.topFocusedWindowChanged(packageName, requestedVisibilities); + PerDisplay.this.topFocusedWindowChanged(component, requestedVisibilities); }); } @@ -234,10 +235,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan /** * Called when top focused window changes to determine whether or not to take over insets * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false. - * @param packageName The name of the package that is open in the top focussed window. + * @param component The application component that is open in the top focussed window. * @param requestedVisibilities The insets visibilities requested by the focussed window. */ - default void topFocusedWindowChanged(String packageName, + default void topFocusedWindowChanged(ComponentName component, InsetsVisibilities requestedVisibilities) {} /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java deleted file mode 100644 index b87cf47dd93f..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2021 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.wm.shell.compatui; - -import com.android.wm.shell.common.annotations.ExternalThread; - -/** - * Interface to engage compat UI. - */ -@ExternalThread -public interface CompatUI { - /** - * Called when the keyguard showing state changes. Removes all compat UIs if the - * keyguard is now showing. - * - * <p>Note that if the keyguard is occluded it will also be considered showing. - * - * @param showing indicates if the keyguard is now showing. - */ - void onKeyguardShowingChanged(boolean showing); -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 99b32a677abe..db8d9d423aca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -39,9 +39,10 @@ import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListen import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; +import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.transition.Transitions; import java.lang.ref.WeakReference; @@ -58,7 +59,7 @@ import dagger.Lazy; * activities are in compatibility mode. */ public class CompatUIController implements OnDisplaysChangedListener, - DisplayImeController.ImePositionProcessor { + DisplayImeController.ImePositionProcessor, KeyguardChangeListener { /** Callback for compat UI interaction. */ public interface CompatUICallback { @@ -100,13 +101,13 @@ public class CompatUIController implements OnDisplaysChangedListener, private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0); private final Context mContext; + private final ShellController mShellController; private final DisplayController mDisplayController; private final DisplayInsetsController mDisplayInsetsController; private final DisplayImeController mImeController; private final SyncTransactionQueue mSyncQueue; private final ShellExecutor mMainExecutor; private final Lazy<Transitions> mTransitionsLazy; - private final CompatUIImpl mImpl = new CompatUIImpl(); private CompatUICallback mCallback; @@ -118,6 +119,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private boolean mKeyguardShowing; public CompatUIController(Context context, + ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, DisplayImeController imeController, @@ -125,6 +127,7 @@ public class CompatUIController implements OnDisplaysChangedListener, ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) { mContext = context; + mShellController = shellController; mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; mImeController = imeController; @@ -134,11 +137,7 @@ public class CompatUIController implements OnDisplaysChangedListener, mDisplayController.addDisplayWindowListener(this); mImeController.addPositionProcessor(this); mCompatUIHintsState = new CompatUIHintsState(); - } - - /** Returns implementation of {@link CompatUI}. */ - public CompatUI asCompatUI() { - return mImpl; + shellController.addKeyguardChangeListener(this); } /** Sets the callback for UI interactions. */ @@ -223,9 +222,10 @@ public class CompatUIController implements OnDisplaysChangedListener, layout -> layout.updateVisibility(showOnDisplay(displayId))); } - @VisibleForTesting - void onKeyguardShowingChanged(boolean showing) { - mKeyguardShowing = showing; + @Override + public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) { + mKeyguardShowing = visible; // Hide the compat UIs when keyguard is showing. forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId()))); } @@ -373,19 +373,6 @@ public class CompatUIController implements OnDisplaysChangedListener, } } - /** - * The interface for calls from outside the Shell, within the host process. - */ - @ExternalThread - private class CompatUIImpl implements CompatUI { - @Override - public void onKeyguardShowingChanged(boolean showing) { - mMainExecutor.execute(() -> { - CompatUIController.this.onKeyguardShowingChanged(showing); - }); - } - } - /** An implementation of {@link OnInsetsChangedListener} for a given display id. */ private class PerDisplayOnInsetsChangedListener implements OnInsetsChangedListener { final int mDisplayId; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index 1ea5e21a2c1e..81904e291ad1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -48,6 +48,7 @@ import com.android.wm.shell.pip.tv.TvPipNotificationController; import com.android.wm.shell.pip.tv.TvPipTaskOrganizer; import com.android.wm.shell.pip.tv.TvPipTransition; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.transition.Transitions; import java.util.Optional; @@ -64,6 +65,7 @@ public abstract class TvPipModule { @Provides static Optional<Pip> providePip( Context context, + ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, @@ -81,6 +83,7 @@ public abstract class TvPipModule { return Optional.of( TvPipController.create( context, + shellController, tvPipBoundsState, tvPipBoundsAlgorithm, tvPipBoundsController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 2ea111b113d8..586eab07649f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -38,6 +38,7 @@ import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.TaskViewFactoryController; import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; @@ -56,15 +57,12 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; -import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; -import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; import com.android.wm.shell.onehanded.OneHanded; @@ -82,8 +80,8 @@ import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm; -import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; -import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInterface; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; @@ -159,16 +157,13 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static DragAndDropController provideDragAndDropController(Context context, - DisplayController displayController, UiEventLogger uiEventLogger, - IconProvider iconProvider, @ShellMainThread ShellExecutor mainExecutor) { - return new DragAndDropController(context, displayController, uiEventLogger, iconProvider, - mainExecutor); - } - - @WMSingleton - @Provides - static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) { - return Optional.of(dragAndDropController.asDragAndDrop()); + ShellController shellController, + DisplayController displayController, + UiEventLogger uiEventLogger, + IconProvider iconProvider, + @ShellMainThread ShellExecutor mainExecutor) { + return new DragAndDropController(context, shellController, displayController, uiEventLogger, + iconProvider, mainExecutor); } @WMSingleton @@ -201,18 +196,14 @@ public abstract class WMShellBaseModule { } @WMSingleton - @Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) { - return Optional.of(compatUIController.asCompatUI()); - } - - @WMSingleton @Provides static CompatUIController provideCompatUIController(Context context, + ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, DisplayImeController imeController, SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) { - return new CompatUIController(context, displayController, displayInsetsController, - imeController, syncQueue, mainExecutor, transitionsLazy); + return new CompatUIController(context, shellController, displayController, + displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy); } @WMSingleton @@ -369,17 +360,12 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static Optional<HideDisplayCutout> provideHideDisplayCutout( - Optional<HideDisplayCutoutController> hideDisplayCutoutController) { - return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout()); - } - - @WMSingleton - @Provides static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context, - DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) { + ShellController shellController, DisplayController displayController, + @ShellMainThread ShellExecutor mainExecutor) { return Optional.ofNullable( - HideDisplayCutoutController.create(context, displayController, mainExecutor)); + HideDisplayCutoutController.create(context, shellController, displayController, + mainExecutor)); } // @@ -408,23 +394,6 @@ public abstract class WMShellBaseModule { } // - // Task to Surface communication - // - - @WMSingleton - @Provides - static Optional<TaskSurfaceHelper> provideTaskSurfaceHelper( - Optional<TaskSurfaceHelperController> taskSurfaceController) { - return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper()); - } - - @Provides - static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController( - ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { - return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor)); - } - - // // Pip (optional feature) // @@ -621,6 +590,34 @@ public abstract class WMShellBaseModule { taskViewTransitions); } + + // + // ActivityEmbedding + // + + @WMSingleton + @Provides + static Optional<ActivityEmbeddingController> provideActivityEmbeddingController( + Context context, Transitions transitions) { + return Optional.of(new ActivityEmbeddingController(context, transitions)); + } + + // + // SysUI -> Shell interface + // + + @WMSingleton + @Provides + static ShellInterface provideShellSysuiCallbacks(ShellController shellController) { + return shellController.asShell(); + } + + @WMSingleton + @Provides + static ShellController provideShellController(@ShellMainThread ShellExecutor mainExecutor) { + return new ShellController(mainExecutor); + } + // // Misc // @@ -647,6 +644,7 @@ public abstract class WMShellBaseModule { Optional<UnfoldTransitionHandler> unfoldTransitionHandler, Optional<FreeformTaskListener<?>> freeformTaskListener, Optional<RecentTasksController> recentTasksOptional, + Optional<ActivityEmbeddingController> activityEmbeddingOptional, Transitions transitions, StartingWindowController startingWindow, @ShellMainThread ShellExecutor mainExecutor) { @@ -664,6 +662,7 @@ public abstract class WMShellBaseModule { unfoldTransitionHandler, freeformTaskListener, recentTasksOptional, + activityEmbeddingOptional, transitions, startingWindow, mainExecutor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 189743728840..0f33f4c08e9c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -71,6 +71,7 @@ import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.UnfoldAnimationController; @@ -134,6 +135,7 @@ public abstract class WMShellModule { @WMSingleton @Provides static BubbleController provideBubbleController(Context context, + ShellController shellController, BubbleData data, FloatingContentCoordinator floatingContentCoordinator, IStatusBarService statusBarService, @@ -153,7 +155,7 @@ public abstract class WMShellModule { @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { - return new BubbleController(context, data, null /* synchronizer */, + return new BubbleController(context, shellController, data, null /* synchronizer */, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps, mainExecutor), statusBarService, windowManager, windowManagerShellWrapper, userManager, @@ -205,12 +207,14 @@ public abstract class WMShellModule { @Provides @DynamicOverride static OneHandedController provideOneHandedController(Context context, + ShellController shellController, WindowManager windowManager, DisplayController displayController, DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { - return OneHandedController.create(context, windowManager, displayController, displayLayout, - taskStackListener, jankMonitor, uiEventLogger, mainExecutor, mainHandler); + return OneHandedController.create(context, shellController, windowManager, + displayController, displayLayout, taskStackListener, jankMonitor, uiEventLogger, + mainExecutor, mainHandler); } // @@ -221,6 +225,7 @@ public abstract class WMShellModule { @Provides @DynamicOverride static SplitScreenController provideSplitScreenController( + ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @@ -230,7 +235,7 @@ public abstract class WMShellModule { DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks) { - return new SplitScreenController(shellTaskOrganizer, syncQueue, context, + return new SplitScreenController(shellController, shellTaskOrganizer, syncQueue, context, rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, recentTasks); @@ -242,7 +247,8 @@ public abstract class WMShellModule { @WMSingleton @Provides - static Optional<Pip> providePip(Context context, DisplayController displayController, + static Optional<Pip> providePip(Context context, + ShellController shellController, DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, @@ -254,7 +260,7 @@ public abstract class WMShellModule { PipParamsChangedForwarder pipParamsChangedForwarder, Optional<OneHandedController> oneHandedController, @ShellMainThread ShellExecutor mainExecutor) { - return Optional.ofNullable(PipController.create(context, displayController, + return Optional.ofNullable(PipController.create(context, shellController, displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md index 68f970ff48df..0dd50b1bee68 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md @@ -52,4 +52,14 @@ For example, you might have: call, and the callback posts to the main SysUI thread Adding an interface to a Shell component may seem like a lot of boiler plate, but is currently -necessary to maintain proper threading and logic isolation.
\ No newline at end of file +necessary to maintain proper threading and logic isolation. + +## Configuration changes & other SysUI events + +Aside from direct calls into Shell controllers for exposed features, the Shell also receives +common event callbacks from SysUI via the `ShellController`. This includes things like: + +- Configuration changes +- TODO: Shell init +- TODO: Shell command +- TODO: Keyguard events
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 95de2dc61a43..c5df53b6dbc8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -60,6 +60,8 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; import java.util.ArrayList; import java.util.Optional; @@ -68,21 +70,20 @@ import java.util.Optional; * Handles the global drag and drop handling for the Shell. */ public class DragAndDropController implements DisplayController.OnDisplaysChangedListener, - View.OnDragListener { + View.OnDragListener, ConfigurationChangeListener { private static final String TAG = DragAndDropController.class.getSimpleName(); private final Context mContext; + private final ShellController mShellController; private final DisplayController mDisplayController; private final DragAndDropEventLogger mLogger; private final IconProvider mIconProvider; private SplitScreenController mSplitScreen; private ShellExecutor mMainExecutor; - private DragAndDropImpl mImpl; private ArrayList<DragAndDropListener> mListeners = new ArrayList<>(); private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>(); - private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); /** * Listener called during drag events, currently just onDragStarted. @@ -92,23 +93,24 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange void onDragStarted(); } - public DragAndDropController(Context context, DisplayController displayController, - UiEventLogger uiEventLogger, IconProvider iconProvider, ShellExecutor mainExecutor) { + public DragAndDropController(Context context, + ShellController shellController, + DisplayController displayController, + UiEventLogger uiEventLogger, + IconProvider iconProvider, + ShellExecutor mainExecutor) { mContext = context; + mShellController = shellController; mDisplayController = displayController; mLogger = new DragAndDropEventLogger(uiEventLogger); mIconProvider = iconProvider; mMainExecutor = mainExecutor; - mImpl = new DragAndDropImpl(); - } - - public DragAndDrop asDragAndDrop() { - return mImpl; } public void initialize(Optional<SplitScreenController> splitscreen) { mSplitScreen = splitscreen.orElse(null); mDisplayController.addDisplayWindowListener(this); + mShellController.addConfigurationChangeListener(this); } /** Adds a listener to be notified of drag and drop events. */ @@ -310,13 +312,15 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange return mimeTypes; } - private void onThemeChange() { + @Override + public void onThemeChanged() { for (int i = 0; i < mDisplayDropTargets.size(); i++) { mDisplayDropTargets.get(i).dragLayout.onThemeChange(); } } - private void onConfigChanged(Configuration newConfig) { + @Override + public void onConfigurationChanged(Configuration newConfig) { for (int i = 0; i < mDisplayDropTargets.size(); i++) { mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig); } @@ -342,21 +346,4 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange dragLayout = dl; } } - - private class DragAndDropImpl implements DragAndDrop { - - @Override - public void onThemeChanged() { - mMainExecutor.execute(() -> { - DragAndDropController.this.onThemeChange(); - }); - } - - @Override - public void onConfigChanged(Configuration newConfig) { - mMainExecutor.execute(() -> { - DragAndDropController.this.onConfigChanged(newConfig); - }); - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java index 23f76ca5f6ae..665b035bc41c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java @@ -19,7 +19,6 @@ package com.android.wm.shell.hidedisplaycutout; import android.content.Context; import android.content.res.Configuration; import android.os.SystemProperties; -import android.util.Slog; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -27,20 +26,20 @@ import androidx.annotation.VisibleForTesting; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; import java.io.PrintWriter; -import java.util.concurrent.TimeUnit; /** * Manages the hide display cutout status. */ -public class HideDisplayCutoutController { +public class HideDisplayCutoutController implements ConfigurationChangeListener { private static final String TAG = "HideDisplayCutoutController"; private final Context mContext; + private final ShellController mShellController; private final HideDisplayCutoutOrganizer mOrganizer; - private final ShellExecutor mMainExecutor; - private final HideDisplayCutoutImpl mImpl = new HideDisplayCutoutImpl(); @VisibleForTesting boolean mEnabled; @@ -49,8 +48,9 @@ public class HideDisplayCutoutController { * supported. */ @Nullable - public static HideDisplayCutoutController create( - Context context, DisplayController displayController, ShellExecutor mainExecutor) { + public static HideDisplayCutoutController create(Context context, + ShellController shellController, DisplayController displayController, + ShellExecutor mainExecutor) { // The SystemProperty is set for devices that support this feature and is used to control // whether to create the HideDisplayCutout instance. // It's defined in the device.mk (e.g. device/google/crosshatch/device.mk). @@ -60,19 +60,16 @@ public class HideDisplayCutoutController { HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer(context, displayController, mainExecutor); - return new HideDisplayCutoutController(context, organizer, mainExecutor); + return new HideDisplayCutoutController(context, shellController, organizer); } - HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer, - ShellExecutor mainExecutor) { + HideDisplayCutoutController(Context context, ShellController shellController, + HideDisplayCutoutOrganizer organizer) { mContext = context; + mShellController = shellController; mOrganizer = organizer; - mMainExecutor = mainExecutor; updateStatus(); - } - - public HideDisplayCutout asHideDisplayCutout() { - return mImpl; + mShellController.addConfigurationChangeListener(this); } @VisibleForTesting @@ -94,7 +91,8 @@ public class HideDisplayCutoutController { } } - private void onConfigurationChanged(Configuration newConfig) { + @Override + public void onConfigurationChanged(Configuration newConfig) { updateStatus(); } @@ -107,13 +105,4 @@ public class HideDisplayCutoutController { pw.println(mEnabled); mOrganizer.dump(pw); } - - private class HideDisplayCutoutImpl implements HideDisplayCutout { - @Override - public void onConfigurationChanged(Configuration newConfig) { - mMainExecutor.execute(() -> { - HideDisplayCutoutController.this.onConfigurationChanged(newConfig); - }); - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java index b00182f36cc8..76c0f41997ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java @@ -16,7 +16,6 @@ package com.android.wm.shell.onehanded; -import android.content.res.Configuration; import android.os.SystemProperties; import com.android.wm.shell.common.annotations.ExternalThread; @@ -83,17 +82,7 @@ public interface OneHanded { void registerTransitionCallback(OneHandedTransitionCallback callback); /** - * Receive onConfigurationChanged() events - */ - void onConfigChanged(Configuration newConfig); - - /** * Notifies when user switch complete */ void onUserSwitch(int userId); - - /** - * Notifies when keyguard visibility changed - */ - void onKeyguardVisibilityChanged(boolean showing); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 3b9fcc78c932..c67247603f2a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -54,6 +54,9 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellController; import java.io.PrintWriter; @@ -61,7 +64,8 @@ import java.io.PrintWriter; * Manages and manipulates the one handed states, transitions, and gesture for phones. */ public class OneHandedController implements RemoteCallable<OneHandedController>, - DisplayChangeController.OnDisplayChangingListener { + DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener, + KeyguardChangeListener { private static final String TAG = "OneHandedController"; private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = @@ -81,6 +85,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, private Context mContext; + private final ShellController mShellController; private final AccessibilityManager mAccessibilityManager; private final DisplayController mDisplayController; private final OneHandedSettingsUtil mOneHandedSettingsUtil; @@ -188,8 +193,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported. */ public static OneHandedController create( - Context context, WindowManager windowManager, DisplayController displayController, - DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, + Context context, ShellController shellController, WindowManager windowManager, + DisplayController displayController, DisplayLayout displayLayout, + TaskStackListenerImpl taskStackListener, InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) { OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil(); @@ -207,14 +213,15 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, context, displayLayout, settingsUtil, animationController, tutorialHandler, jankMonitor, mainExecutor); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); - return new OneHandedController(context, displayController, organizer, touchHandler, - tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, - oneHandedUiEventsLogger, taskStackListener, + return new OneHandedController(context, shellController, displayController, organizer, + touchHandler, tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, + oneHandedState, oneHandedUiEventsLogger, taskStackListener, mainExecutor, mainHandler); } @VisibleForTesting OneHandedController(Context context, + ShellController shellController, DisplayController displayController, OneHandedDisplayAreaOrganizer displayAreaOrganizer, OneHandedTouchHandler touchHandler, @@ -228,6 +235,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; + mShellController = shellController; mOneHandedSettingsUtil = settingsUtil; mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil; mDisplayAreaOrganizer = displayAreaOrganizer; @@ -272,6 +280,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mAccessibilityStateChangeListener); mState.addSListeners(mTutorialHandler); + mShellController.addConfigurationChangeListener(this); + mShellController.addKeyguardChangeListener(this); } public OneHanded asOneHanded() { @@ -587,7 +597,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mLockedDisabled = locked && !enabled; } - private void onConfigChanged(Configuration newConfig) { + @Override + public void onConfigurationChanged(Configuration newConfig) { if (mTutorialHandler == null) { return; } @@ -597,8 +608,11 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mTutorialHandler.onConfigurationChanged(); } - private void onKeyguardVisibilityChanged(boolean showing) { - mKeyguardShowing = showing; + @Override + public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) { + mKeyguardShowing = visible; + stopOneHanded(); } private void onUserSwitch(int newUserId) { @@ -743,25 +757,11 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, } @Override - public void onConfigChanged(Configuration newConfig) { - mMainExecutor.execute(() -> { - OneHandedController.this.onConfigChanged(newConfig); - }); - } - - @Override public void onUserSwitch(int userId) { mMainExecutor.execute(() -> { OneHandedController.this.onUserSwitch(userId); }); } - - @Override - public void onKeyguardVisibilityChanged(boolean showing) { - mMainExecutor.execute(() -> { - OneHandedController.this.onKeyguardVisibilityChanged(showing); - }); - } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index bbc47e47afc5..38631ce26cd1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -16,7 +16,6 @@ package com.android.wm.shell.pip; -import android.content.res.Configuration; import android.graphics.Rect; import com.android.wm.shell.common.annotations.ExternalThread; @@ -44,24 +43,6 @@ public interface Pip { } /** - * Called when configuration is changed. - */ - default void onConfigurationChanged(Configuration newConfig) { - } - - /** - * Called when display size or font size of settings changed - */ - default void onDensityOrFontScaleChanged() { - } - - /** - * Called when overlay package change invoked. - */ - default void onOverlayChanged() { - } - - /** * Called when SysUI state changed. * * @param isSysUiStateValid Is SysUI state valid or not. @@ -120,23 +101,6 @@ public interface Pip { default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { } /** - * Called when the visibility of keyguard is changed. - * @param showing {@code true} if keyguard is now showing, {@code false} otherwise. - * @param animating {@code true} if system is animating between keyguard and surface behind, - * this only makes sense when showing is {@code false}. - */ - default void onKeyguardVisibilityChanged(boolean showing, boolean animating) { } - - /** - * Called when the dismissing animation keyguard and surfaces behind is finished. - * See also {@link #onKeyguardVisibilityChanged(boolean, boolean)}. - * - * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of - * keyguard dismiss animation. - */ - default void onKeyguardDismissAnimationFinished() { } - - /** * Dump the current state and information if need. * * @param pw The stream to dump information to. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 3000998f210d..420d6067f420 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -87,6 +87,9 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; @@ -100,7 +103,7 @@ import java.util.function.Consumer; * Manages the picture-in-picture (PIP) UI and states for Phones. */ public class PipController implements PipTransitionController.PipTransitionCallback, - RemoteCallable<PipController> { + RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener { private static final String TAG = "PipController"; private Context mContext; @@ -119,6 +122,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb private TaskStackListenerImpl mTaskStackListener; private PipParamsChangedForwarder mPipParamsChangedForwarder; private Optional<OneHandedController> mOneHandedController; + private final ShellController mShellController; protected final PipImpl mImpl; private final Rect mTmpInsetBounds = new Rect(); @@ -290,13 +294,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb * Instantiates {@link PipController}, returns {@code null} if the feature not supported. */ @Nullable - public static Pip create(Context context, DisplayController displayController, - PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, - PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, - PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, + public static Pip create(Context context, + ShellController shellController, + DisplayController displayController, + PipAppOpsListener pipAppOpsListener, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipKeepClearAlgorithm pipKeepClearAlgorithm, + PipBoundsState pipBoundsState, + PipMotionHelper pipMotionHelper, + PipMediaController pipMediaController, + PhonePipMenuController phonePipMenuController, + PipTaskOrganizer pipTaskOrganizer, PipTransitionState pipTransitionState, - PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, + PipTouchHandler pipTouchHandler, + PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, @@ -308,9 +319,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb return null; } - return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm, - pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController, - phonePipMenuController, pipTaskOrganizer, pipTransitionState, + return new PipController(context, shellController, displayController, pipAppOpsListener, + pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, + pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor) @@ -318,6 +329,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } protected PipController(Context context, + ShellController shellController, DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, @@ -343,6 +355,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } mContext = context; + mShellController = shellController; mImpl = new PipImpl(); mWindowManagerShellWrapper = windowManagerShellWrapper; mDisplayController = displayController; @@ -513,6 +526,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb } }); }); + + mShellController.addConfigurationChangeListener(this); + mShellController.addKeyguardChangeListener(this); } @Override @@ -525,18 +541,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb return mMainExecutor; } - private void onConfigurationChanged(Configuration newConfig) { + @Override + public void onConfigurationChanged(Configuration newConfig) { mPipBoundsAlgorithm.onConfigurationChanged(mContext); mTouchHandler.onConfigurationChanged(); mPipBoundsState.onConfigurationChanged(); } - private void onDensityOrFontScaleChanged() { + @Override + public void onDensityOrFontScaleChanged() { mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext); onPipResourceDimensionsChanged(); } - private void onOverlayChanged() { + @Override + public void onThemeChanged() { mTouchHandler.onOverlayChanged(); onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()), false /* saveRestoreSnapFraction */); @@ -644,21 +663,24 @@ public class PipController implements PipTransitionController.PipTransitionCallb * finished first to reset the visibility of PiP window. * See also {@link #onKeyguardDismissAnimationFinished()} */ - private void onKeyguardVisibilityChanged(boolean keyguardShowing, boolean animating) { + @Override + public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) { if (!mPipTaskOrganizer.isInPip()) { return; } - if (keyguardShowing) { + if (visible) { mIsKeyguardShowingOrAnimating = true; hidePipMenu(null /* onStartCallback */, null /* onEndCallback */); mPipTaskOrganizer.setPipVisibility(false); - } else if (!animating) { + } else if (!animatingDismiss) { mIsKeyguardShowingOrAnimating = false; mPipTaskOrganizer.setPipVisibility(true); } } - private void onKeyguardDismissAnimationFinished() { + @Override + public void onKeyguardDismissAnimationFinished() { if (mPipTaskOrganizer.isInPip()) { mIsKeyguardShowingOrAnimating = false; mPipTaskOrganizer.setPipVisibility(true); @@ -923,27 +945,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void onConfigurationChanged(Configuration newConfig) { - mMainExecutor.execute(() -> { - PipController.this.onConfigurationChanged(newConfig); - }); - } - - @Override - public void onDensityOrFontScaleChanged() { - mMainExecutor.execute(() -> { - PipController.this.onDensityOrFontScaleChanged(); - }); - } - - @Override - public void onOverlayChanged() { - mMainExecutor.execute(() -> { - PipController.this.onOverlayChanged(); - }); - } - - @Override public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) { mMainExecutor.execute(() -> { PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag); @@ -1000,18 +1001,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void onKeyguardVisibilityChanged(boolean showing, boolean animating) { - mMainExecutor.execute(() -> { - PipController.this.onKeyguardVisibilityChanged(showing, animating); - }); - } - - @Override - public void onKeyguardDismissAnimationFinished() { - mMainExecutor.execute(PipController.this::onKeyguardDismissAnimationFinished); - } - - @Override public void dump(PrintWriter pw) { try { mMainExecutor.executeBlocking(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index fa48def9c7d7..a24d9618032d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -49,6 +49,8 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -61,7 +63,8 @@ import java.util.Set; */ public class TvPipController implements PipTransitionController.PipTransitionCallback, TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate, - TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener { + TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener, + ConfigurationChangeListener { private static final String TAG = "TvPipController"; static final boolean DEBUG = false; @@ -93,6 +96,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final Context mContext; + private final ShellController mShellController; private final TvPipBoundsState mTvPipBoundsState; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; private final TvPipBoundsController mTvPipBoundsController; @@ -117,6 +121,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public static Pip create( Context context, + ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, @@ -133,6 +138,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal ShellExecutor mainExecutor) { return new TvPipController( context, + shellController, tvPipBoundsState, tvPipBoundsAlgorithm, tvPipBoundsController, @@ -151,6 +157,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private TvPipController( Context context, + ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, @@ -167,6 +174,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal ShellExecutor mainExecutor) { mContext = context; mMainExecutor = mainExecutor; + mShellController = shellController; mTvPipBoundsState = tvPipBoundsState; mTvPipBoundsState.setDisplayId(context.getDisplayId()); @@ -193,9 +201,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal registerTaskStackListenerCallback(taskStackListener); registerWmShellPinnedStackListener(wmShell); displayController.addDisplayWindowListener(this); + + mShellController.addConfigurationChangeListener(this); } - private void onConfigurationChanged(Configuration newConfig) { + @Override + public void onConfigurationChanged(Configuration newConfig) { if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState)); @@ -669,13 +680,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private class TvPipImpl implements Pip { @Override - public void onConfigurationChanged(Configuration newConfig) { - mMainExecutor.execute(() -> { - TvPipController.this.onConfigurationChanged(newConfig); - }); - } - - @Override public void registerSessionListenerForCurrentUser() { mMainExecutor.execute(() -> { TvPipController.this.registerSessionListenerForCurrentUser(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index e31e0d674bec..b2961518b66a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -44,6 +44,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SHELL), WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), + WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + Consts.TAG_WM_SHELL), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 29b6311e5041..e73b799b7a3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -77,12 +77,6 @@ public interface SplitScreen { return null; } - /** - * Called when the visibility of the keyguard changes. - * @param showing Indicates if the keyguard is now visible. - */ - void onKeyguardVisibilityChanged(boolean showing); - /** Called when device waking up finished. */ void onFinishedWakingUp(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 249468d1446c..53ec39d954c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -24,8 +24,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; -import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; -import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -64,7 +62,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; -import com.android.internal.util.ArrayUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; @@ -83,6 +80,8 @@ import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreen.StageType; +import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; @@ -102,7 +101,7 @@ import java.util.concurrent.Executor; */ // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen. public class SplitScreenController implements DragAndDropPolicy.Starter, - RemoteCallable<SplitScreenController>, ShellTaskOrganizer.FocusListener { + RemoteCallable<SplitScreenController>, KeyguardChangeListener { private static final String TAG = SplitScreenController.class.getSimpleName(); public static final int EXIT_REASON_UNKNOWN = 0; @@ -130,6 +129,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Retention(RetentionPolicy.SOURCE) @interface ExitReason{} + private final ShellController mShellController; private final ShellTaskOrganizer mTaskOrganizer; private final SyncTransactionQueue mSyncQueue; private final Context mContext; @@ -150,9 +150,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, // outside the bounds of the roots by being reparented into a higher level fullscreen container private SurfaceControl mSplitTasksContainerLayer; - private ActivityManager.RunningTaskInfo mFocusingTaskInfo; - - public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer, + public SplitScreenController(ShellController shellController, + ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellExecutor mainExecutor, DisplayController displayController, @@ -160,6 +159,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks) { + mShellController = shellController; mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; mContext = context; @@ -173,7 +173,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mLogger = new SplitscreenEventLogger(); mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; - mTaskOrganizer.addFocusListener(this); } public SplitScreen asSplitScreen() { @@ -190,12 +189,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mMainExecutor; } - @Override - public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { - mFocusingTaskInfo = taskInfo; - } - public void onOrganizerRegistered() { + mShellController.addKeyguardChangeListener(this); if (mStageCoordinator == null) { // TODO: Multi-display mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, @@ -213,6 +208,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator; } + public ActivityManager.RunningTaskInfo getFocusingTaskInfo() { + return mStageCoordinator.getFocusingTaskInfo(); + } + + public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) { + return mStageCoordinator.isValidToEnterSplitScreen(taskInfo); + } + @Nullable public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) { if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) { @@ -228,12 +231,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; } - public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) { - return taskInfo.supportsMultiWindow - && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()) - && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode()); - } - public @SplitPosition int getSplitPosition(int taskId) { return mStageCoordinator.getSplitPosition(taskId); } @@ -292,8 +289,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason); } - public void onKeyguardVisibilityChanged(boolean showing) { - mStageCoordinator.onKeyguardVisibilityChanged(showing); + @Override + public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) { + mStageCoordinator.onKeyguardVisibilityChanged(visible); } public void onFinishedWakingUp() { @@ -470,8 +469,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return Objects.equals(launchingActivity, pairedActivity); } - if (mFocusingTaskInfo != null && isValidToEnterSplitScreen(mFocusingTaskInfo)) { - return Objects.equals(mFocusingTaskInfo.baseIntent.getComponent(), launchingActivity); + final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); + if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { + return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); } return false; @@ -666,13 +666,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override - public void onKeyguardVisibilityChanged(boolean showing) { - mMainExecutor.execute(() -> { - SplitScreenController.this.onKeyguardVisibilityChanged(showing); - }); - } - - @Override public void onFinishedWakingUp() { mMainExecutor.execute(() -> { SplitScreenController.this.onFinishedWakingUp(); 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 3e659464e951..2229e26b9343 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 @@ -35,6 +35,8 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; +import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; +import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -98,6 +100,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.ArrayUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -138,7 +141,7 @@ import java.util.Optional; */ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler, - ShellTaskOrganizer.TaskListener { + ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener { private static final String TAG = StageCoordinator.class.getSimpleName(); @@ -176,6 +179,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final Rect mTempRect1 = new Rect(); private final Rect mTempRect2 = new Rect(); + private ActivityManager.RunningTaskInfo mFocusingTaskInfo; + /** * A single-top root task which the split divider attached to. */ @@ -255,6 +260,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayController.addDisplayWindowListener(this); mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); + mTaskOrganizer.addFocusListener(this); } @VisibleForTesting @@ -1226,12 +1232,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } } else if (isSideStage && !mMainStage.isActive()) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - mSplitLayout.init(); - prepareEnterSplitScreen(wct); - mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */)); + if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mSideStage.removeAllTasks(wct, true); + wct.reorder(mRootTaskInfo.token, false /* onTop */); + mTaskOrganizer.applyTransaction(wct); + Slog.i(TAG, "cancel entering split screen, reason = " + + exitReasonToString(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW)); + } else { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mSplitLayout.init(); + prepareEnterSplitScreen(wct); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */)); + } } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { mShouldUpdateRecents = true; @@ -1246,6 +1261,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) { + return taskInfo.supportsMultiWindow + && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()) + && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode()); + } + + ActivityManager.RunningTaskInfo getFocusingTaskInfo() { + return mFocusingTaskInfo; + } + + @Override + public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + mFocusingTaskInfo = taskInfo; + } + @Override public void onSnappedToDismiss(boolean bottomOrRight, int reason) { final boolean mainStageToTop = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ConfigurationChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ConfigurationChangeListener.java new file mode 100644 index 000000000000..2fca8f0ecc76 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ConfigurationChangeListener.java @@ -0,0 +1,51 @@ +/* + * 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.wm.shell.sysui; + +import android.content.res.Configuration; + +/** + * Callbacks for when the configuration changes. + */ +public interface ConfigurationChangeListener { + + /** + * Called when a configuration changes. This precedes all the following callbacks. + */ + default void onConfigurationChanged(Configuration newConfiguration) {} + + /** + * Convenience method to the above, called when the density or font scale changes. + */ + default void onDensityOrFontScaleChanged() {} + + /** + * Convenience method to the above, called when the smallest screen width changes. + */ + default void onSmallestScreenWidthChanged() {} + + /** + * Convenience method to the above, called when the system theme changes, including dark/light + * UI_MODE changes. + */ + default void onThemeChanged() {} + + /** + * Convenience method to the above, called when the local list or layout direction changes. + */ + default void onLocaleOrLayoutDirectionChanged() {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java new file mode 100644 index 000000000000..1c0b35894acd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.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.wm.shell.sysui; + +/** + * Callbacks for when the keyguard changes. + */ +public interface KeyguardChangeListener { + /** + * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded). + */ + default void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) {} + + /** + * Notifies the Shell when the keyguard dismiss animation has finished. + * + * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of + * keyguard dismiss animation. + */ + default void onKeyguardDismissAnimationFinished() {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java new file mode 100644 index 000000000000..837acecef56f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -0,0 +1,187 @@ +/* + * 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.wm.shell.sysui; + +import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; +import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE; +import static android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION; +import static android.content.pm.ActivityInfo.CONFIG_LOCALE; +import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; +import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS; + +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ExternalThread; + +import java.io.PrintWriter; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Handles event callbacks from SysUI that can be used within the Shell. + */ +public class ShellController { + private static final String TAG = ShellController.class.getSimpleName(); + + private final ShellExecutor mMainExecutor; + private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl(); + + private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners = + new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners = + new CopyOnWriteArrayList<>(); + private Configuration mLastConfiguration; + + + public ShellController(ShellExecutor mainExecutor) { + mMainExecutor = mainExecutor; + } + + /** + * Returns the external interface to this controller. + */ + public ShellInterface asShell() { + return mImpl; + } + + /** + * Adds a new configuration listener. The configuration change callbacks are not made in any + * particular order. + */ + public void addConfigurationChangeListener(ConfigurationChangeListener listener) { + mConfigChangeListeners.remove(listener); + mConfigChangeListeners.add(listener); + } + + /** + * Removes an existing configuration listener. + */ + public void removeConfigurationChangeListener(ConfigurationChangeListener listener) { + mConfigChangeListeners.remove(listener); + } + + /** + * Adds a new Keyguard listener. The Keyguard change callbacks are not made in any + * particular order. + */ + public void addKeyguardChangeListener(KeyguardChangeListener listener) { + mKeyguardChangeListeners.remove(listener); + mKeyguardChangeListeners.add(listener); + } + + /** + * Removes an existing Keyguard listener. + */ + public void removeKeyguardChangeListener(KeyguardChangeListener listener) { + mKeyguardChangeListeners.remove(listener); + } + + @VisibleForTesting + void onConfigurationChanged(Configuration newConfig) { + // The initial config is send on startup and doesn't trigger listener callbacks + if (mLastConfiguration == null) { + mLastConfiguration = new Configuration(newConfig); + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Initial Configuration: %s", newConfig); + return; + } + + final int diff = newConfig.diff(mLastConfiguration); + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "New configuration change: %s", newConfig); + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "\tchanges=%s", + Configuration.configurationDiffToString(diff)); + final boolean densityFontScaleChanged = (diff & CONFIG_FONT_SCALE) != 0 + || (diff & ActivityInfo.CONFIG_DENSITY) != 0; + final boolean smallestScreenWidthChanged = (diff & CONFIG_SMALLEST_SCREEN_SIZE) != 0; + final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0 + || (diff & CONFIG_UI_MODE) != 0; + final boolean localOrLayoutDirectionChanged = (diff & CONFIG_LOCALE) != 0 + || (diff & CONFIG_LAYOUT_DIRECTION) != 0; + + // Update the last configuration and call listeners + mLastConfiguration.updateFrom(newConfig); + for (ConfigurationChangeListener listener : mConfigChangeListeners) { + listener.onConfigurationChanged(newConfig); + if (densityFontScaleChanged) { + listener.onDensityOrFontScaleChanged(); + } + if (smallestScreenWidthChanged) { + listener.onSmallestScreenWidthChanged(); + } + if (themeChanged) { + listener.onThemeChanged(); + } + if (localOrLayoutDirectionChanged) { + listener.onLocaleOrLayoutDirectionChanged(); + } + } + } + + @VisibleForTesting + void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) { + for (KeyguardChangeListener listener : mKeyguardChangeListeners) { + listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss); + } + } + + @VisibleForTesting + void onKeyguardDismissAnimationFinished() { + for (KeyguardChangeListener listener : mKeyguardChangeListeners) { + listener.onKeyguardDismissAnimationFinished(); + } + } + + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size()); + pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration); + pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size()); + } + + /** + * The interface for calls from outside the Shell, within the host process. + */ + @ExternalThread + private class ShellInterfaceImpl implements ShellInterface { + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + mMainExecutor.execute(() -> + ShellController.this.onConfigurationChanged(newConfiguration)); + } + + @Override + public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) { + mMainExecutor.execute(() -> + ShellController.this.onKeyguardVisibilityChanged(visible, occluded, + animatingDismiss)); + } + + @Override + public void onKeyguardDismissAnimationFinished() { + mMainExecutor.execute(() -> + ShellController.this.onKeyguardDismissAnimationFinished()); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java new file mode 100644 index 000000000000..a15ce5d2b816 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java @@ -0,0 +1,45 @@ +/* + * 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.wm.shell.sysui; + +import android.content.res.Configuration; + +/** + * General interface for notifying the Shell of common SysUI events like configuration or keyguard + * changes. + * + * TODO: Move ShellInit and ShellCommandHandler into this interface + */ +public interface ShellInterface { + + /** + * Notifies the Shell that the configuration has changed. + */ + default void onConfigurationChanged(Configuration newConfiguration) {} + + /** + * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded) or not + * showing, and whether it is animating a dismiss. + */ + default void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) {} + + /** + * Notifies the Shell when the keyguard dismiss animation has finished. + */ + default void onKeyguardDismissAnimationFinished() {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java deleted file mode 100644 index ad9dda619370..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021 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.wm.shell.tasksurfacehelper; - -import android.app.ActivityManager.RunningTaskInfo; -import android.graphics.Rect; -import android.view.SurfaceControl; - -import java.util.concurrent.Executor; -import java.util.function.Consumer; - -/** - * Interface to communicate with a Task's SurfaceControl. - */ -public interface TaskSurfaceHelper { - - /** Sets the METADATA_GAME_MODE for the layer corresponding to the task **/ - default void setGameModeForTask(int taskId, int gameMode) {} - - /** Takes a screenshot for a task **/ - default void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor, - Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {} -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java deleted file mode 100644 index 064d9d1231c1..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2021 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.wm.shell.tasksurfacehelper; - -import android.app.ActivityManager.RunningTaskInfo; -import android.graphics.Rect; -import android.view.SurfaceControl; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.ShellExecutor; - -import java.util.concurrent.Executor; -import java.util.function.Consumer; - -/** - * Intermediary controller that communicates with {@link ShellTaskOrganizer} to send commands - * to SurfaceControl. - */ -public class TaskSurfaceHelperController { - - private final ShellTaskOrganizer mTaskOrganizer; - private final ShellExecutor mMainExecutor; - private final TaskSurfaceHelperImpl mImpl = new TaskSurfaceHelperImpl(); - - public TaskSurfaceHelperController(ShellTaskOrganizer taskOrganizer, - ShellExecutor mainExecutor) { - mTaskOrganizer = taskOrganizer; - mMainExecutor = mainExecutor; - } - - public TaskSurfaceHelper asTaskSurfaceHelper() { - return mImpl; - } - - /** - * Sends a Transaction to set the game mode metadata on the - * corresponding SurfaceControl - */ - public void setGameModeForTask(int taskId, int gameMode) { - mTaskOrganizer.setSurfaceMetadata(taskId, SurfaceControl.METADATA_GAME_MODE, gameMode); - } - - /** - * Take screenshot of the specified task. - */ - public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, - Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) { - mTaskOrganizer.screenshotTask(taskInfo, crop, consumer); - } - - private class TaskSurfaceHelperImpl implements TaskSurfaceHelper { - @Override - public void setGameModeForTask(int taskId, int gameMode) { - mMainExecutor.execute(() -> { - TaskSurfaceHelperController.this.setGameModeForTask(taskId, gameMode); - }); - } - - @Override - public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor, - Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) { - mMainExecutor.execute(() -> { - TaskSurfaceHelperController.this.screenshotTask(taskInfo, crop, - (t) -> executor.execute(() -> consumer.accept(t))); - }); - } - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java index 1effc97a0de3..ace8d365c7af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java @@ -24,6 +24,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; @@ -69,6 +70,7 @@ public class ShellInitImplTest extends ShellTestCase { @Mock private Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler; @Mock private Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional; @Mock private Optional<RecentTasksController> mRecentTasks; + @Mock private Optional<ActivityEmbeddingController> mActivityEmbeddingController; @Mock private Transitions mTransitions; @Mock private StartingWindowController mStartingWindow; @Mock private ShellExecutor mMainExecutor; @@ -82,8 +84,8 @@ public class ShellInitImplTest extends ShellTestCase { mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer, mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional, mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController, - mUnfoldTransitionHandler, mFreeformTaskListenerOptional, mRecentTasks, mTransitions, - mStartingWindow, mMainExecutor); + mUnfoldTransitionHandler, mFreeformTaskListenerOptional, mRecentTasks, + mActivityEmbeddingController, mTransitions, mStartingWindow, mMainExecutor); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java index 3ef3a1f6a3ad..4a7fd3d259da 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.content.ComponentName; import android.os.RemoteException; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; @@ -165,7 +166,7 @@ public class DisplayInsetsControllerTest extends ShellTestCase { int hideInsetsCount = 0; @Override - public void topFocusedWindowChanged(String packageName, + public void topFocusedWindowChanged(ComponentName component, InsetsVisibilities requestedVisibilities) { topFocusedWindowChangedCount++; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 596100dcdead..828c13ecfda6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -53,6 +53,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -78,6 +79,7 @@ public class CompatUIControllerTest extends ShellTestCase { private static final int TASK_ID = 12; private CompatUIController mController; + private @Mock ShellController mMockShellController; private @Mock DisplayController mMockDisplayController; private @Mock DisplayInsetsController mMockDisplayInsetsController; private @Mock DisplayLayout mMockDisplayLayout; @@ -105,7 +107,7 @@ public class CompatUIControllerTest extends ShellTestCase { doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId(); doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean()); doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean()); - mController = new CompatUIController(mContext, mMockDisplayController, + mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController, mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) { @Override @@ -124,6 +126,11 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + public void instantiateController_registerKeyguardChangeListener() { + verify(mMockShellController, times(1)).addKeyguardChangeListener(any()); + } + + @Test public void testListenerRegistered() { verify(mMockDisplayController).addDisplayWindowListener(mController); verify(mMockImeController).addPositionProcessor(mController); @@ -324,7 +331,7 @@ public class CompatUIControllerTest extends ShellTestCase { /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); // Verify that the restart button is hidden after keyguard becomes showing. - mController.onKeyguardShowingChanged(true); + mController.onKeyguardVisibilityChanged(true, false, false); verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); @@ -340,7 +347,7 @@ public class CompatUIControllerTest extends ShellTestCase { false); // Verify button is shown after keyguard becomes not showing. - mController.onKeyguardShowingChanged(false); + mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); @@ -352,7 +359,7 @@ public class CompatUIControllerTest extends ShellTestCase { /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true); - mController.onKeyguardShowingChanged(true); + mController.onKeyguardVisibilityChanged(true, false, false); verify(mMockCompatLayout, times(2)).updateVisibility(false); verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false); @@ -360,7 +367,7 @@ public class CompatUIControllerTest extends ShellTestCase { clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); // Verify button remains hidden after keyguard becomes not showing since IME is showing. - mController.onKeyguardShowingChanged(false); + mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); @@ -378,7 +385,7 @@ public class CompatUIControllerTest extends ShellTestCase { /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true); - mController.onKeyguardShowingChanged(true); + mController.onKeyguardVisibilityChanged(true, false, false); verify(mMockCompatLayout, times(2)).updateVisibility(false); verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false); @@ -392,7 +399,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockLetterboxEduLayout).updateVisibility(false); // Verify button is shown after keyguard becomes not showing. - mController.onKeyguardShowingChanged(false); + mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index 0b43163787f3..e209971998c8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DragEvent.ACTION_DRAG_STARTED; import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -48,6 +49,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ShellController; import org.junit.Before; import org.junit.Test; @@ -66,24 +68,34 @@ public class DragAndDropControllerTest extends ShellTestCase { @Mock private Context mContext; - + @Mock + private ShellController mShellController; @Mock private DisplayController mDisplayController; - @Mock private UiEventLogger mUiEventLogger; - @Mock private DragAndDropController.DragAndDropListener mDragAndDropListener; + @Mock + private IconProvider mIconProvider; + @Mock + private ShellExecutor mMainExecutor; + @Mock + private SplitScreenController mSplitScreenController; private DragAndDropController mController; @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger, - mock(IconProvider.class), mock(ShellExecutor.class)); - mController.initialize(Optional.of(mock(SplitScreenController.class))); + mController = new DragAndDropController(mContext, mShellController, mDisplayController, + mUiEventLogger, mIconProvider, mMainExecutor); + mController.initialize(Optional.of(mSplitScreenController)); + } + + @Test + public void instantiateController_registerConfigChangeListener() { + verify(mShellController, times(1)).addConfigurationChangeListener(any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java index 7ecd5020d56e..dcc504ad0cdb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java @@ -16,7 +16,9 @@ package com.android.wm.shell.hidedisplaycutout; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; @@ -27,7 +29,7 @@ import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.sysui.ShellController; import org.junit.Before; import org.junit.Test; @@ -42,17 +44,23 @@ public class HideDisplayCutoutControllerTest extends ShellTestCase { private TestableContext mContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getTargetContext(), null); - private HideDisplayCutoutController mHideDisplayCutoutController; @Mock - private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer; + private ShellController mShellController; @Mock - private ShellExecutor mMockMainExecutor; + private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer; + + private HideDisplayCutoutController mHideDisplayCutoutController; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mHideDisplayCutoutController = new HideDisplayCutoutController( - mContext, mMockDisplayAreaOrganizer, mMockMainExecutor); + mContext, mShellController, mMockDisplayAreaOrganizer); + } + + @Test + public void instantiateController_registerConfigChangeListener() { + verify(mShellController, times(1)).addConfigurationChangeListener(any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 8a2bbd75db57..dbf93ae35c18 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -30,6 +30,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -48,6 +49,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.sysui.ShellController; import org.junit.Before; import org.junit.Test; @@ -68,6 +70,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { OneHandedState mSpiedTransitionState; @Mock + ShellController mMockShellController; + @Mock DisplayLayout mDisplayLayout; @Mock DisplayController mMockDisplayController; @@ -123,6 +127,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext); mSpiedOneHandedController = spy(new OneHandedController( mContext, + mMockShellController, mMockDisplayController, mMockDisplayAreaOrganizer, mMockTouchHandler, @@ -139,6 +144,16 @@ public class OneHandedControllerTest extends OneHandedTestCase { } @Test + public void testControllerRegistersConfigChangeListener() { + verify(mMockShellController, times(1)).addConfigurationChangeListener(any()); + } + + @Test + public void testControllerRegistersKeyguardChangeListener() { + verify(mMockShellController, times(1)).addKeyguardChangeListener(any()); + } + + @Test public void testDefaultShouldNotInOneHanded() { // Assert default transition state is STATE_NONE assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java index 7ee7536d454d..e6a8220e081b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java @@ -31,7 +31,6 @@ import static org.mockito.Mockito.when; import android.graphics.Rect; import android.os.Handler; -import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.util.ArrayMap; import android.view.Display; @@ -42,6 +41,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.sysui.ShellController; import org.junit.Before; import org.junit.Test; @@ -52,7 +52,6 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class OneHandedStateTest extends OneHandedTestCase { - private int mCurrentUser = UserHandle.myUserId(); Display mDisplay; DisplayLayout mDisplayLayout; @@ -62,6 +61,8 @@ public class OneHandedStateTest extends OneHandedTestCase { OneHandedState mSpiedState; @Mock + ShellController mMockShellController; + @Mock DisplayController mMockDisplayController; @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @@ -110,6 +111,7 @@ public class OneHandedStateTest extends OneHandedTestCase { mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext); mSpiedOneHandedController = spy(new OneHandedController( mContext, + mMockShellController, mMockDisplayController, mMockDisplayAreaOrganizer, mMockTouchHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index babc9707ef9c..f192514c37ab 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -54,6 +55,7 @@ import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; +import com.android.wm.shell.sysui.ShellController; import org.junit.Before; import org.junit.Test; @@ -73,6 +75,7 @@ import java.util.Set; public class PipControllerTest extends ShellTestCase { private PipController mPipController; + @Mock private ShellController mMockShellController; @Mock private DisplayController mMockDisplayController; @Mock private PhonePipMenuController mMockPhonePipMenuController; @Mock private PipAppOpsListener mMockPipAppOpsListener; @@ -102,7 +105,7 @@ public class PipControllerTest extends ShellTestCase { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(mMockExecutor).execute(any()); - mPipController = new PipController(mContext, mMockDisplayController, + mPipController = new PipController(mContext, mMockShellController, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, @@ -115,6 +118,16 @@ public class PipControllerTest extends ShellTestCase { } @Test + public void instantiatePipController_registerConfigChangeListener() { + verify(mMockShellController, times(1)).addConfigurationChangeListener(any()); + } + + @Test + public void instantiatePipController_registerKeyguardChangeListener() { + verify(mMockShellController, times(1)).addKeyguardChangeListener(any()); + } + + @Test public void instantiatePipController_registersPipTransitionCallback() { verify(mMockPipTransitionController).registerPipTransitionCallback(any()); } @@ -136,7 +149,7 @@ public class PipControllerTest extends ShellTestCase { when(mockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false); when(spyContext.getPackageManager()).thenReturn(mockPackageManager); - assertNull(PipController.create(spyContext, mMockDisplayController, + assertNull(PipController.create(spyContext, mMockShellController, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index c90a8259a9ef..c7a261f32e43 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -24,9 +24,13 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.ComponentName; @@ -43,10 +47,12 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -64,6 +70,7 @@ import java.util.Optional; @RunWith(AndroidJUnit4.class) public class SplitScreenControllerTests extends ShellTestCase { + @Mock ShellController mShellController; @Mock ShellTaskOrganizer mTaskOrganizer; @Mock SyncTransactionQueue mSyncQueue; @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer; @@ -81,21 +88,29 @@ public class SplitScreenControllerTests extends ShellTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - mSplitScreenController = spy(new SplitScreenController(mTaskOrganizer, mSyncQueue, mContext, - mRootTDAOrganizer, mMainExecutor, mDisplayController, mDisplayImeController, - mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, - mRecentTasks)); + mSplitScreenController = spy(new SplitScreenController(mShellController, mTaskOrganizer, + mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController, + mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, + mIconProvider, mRecentTasks)); + } + + @Test + public void testControllerRegistersKeyguardChangeListener() { + when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); + mSplitScreenController.onOrganizerRegistered(); + verify(mShellController, times(1)).addKeyguardChangeListener(any()); } @Test public void testIsLaunchingAdjacently_notInSplitScreen() { doReturn(false).when(mSplitScreenController).isSplitScreenVisible(); + doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any()); // Verify launching the same activity returns true. Intent startIntent = createStartIntent("startActivity"); ActivityManager.RunningTaskInfo focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - mSplitScreenController.onFocusTaskChanged(focusTaskInfo); + doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo(); assertTrue(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); @@ -103,7 +118,7 @@ public class SplitScreenControllerTests extends ShellTestCase { Intent diffIntent = createStartIntent("diffActivity"); focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent); - mSplitScreenController.onFocusTaskChanged(focusTaskInfo); + doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo(); assertFalse(mSplitScreenController.isLaunchingAdjacently( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java new file mode 100644 index 000000000000..1c0e46f7264e --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java @@ -0,0 +1,331 @@ +/* + * 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.wm.shell.sysui; + +import static org.junit.Assert.assertTrue; + +import android.content.res.Configuration; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.ShellExecutor; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Locale; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class ShellControllerTest extends ShellTestCase { + + @Mock + private ShellExecutor mExecutor; + + private ShellController mController; + private TestConfigurationChangeListener mConfigChangeListener; + private TestKeyguardChangeListener mKeyguardChangeListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mKeyguardChangeListener = new TestKeyguardChangeListener(); + mConfigChangeListener = new TestConfigurationChangeListener(); + mController = new ShellController(mExecutor); + mController.onConfigurationChanged(getConfigurationCopy()); + } + + @After + public void tearDown() { + // Do nothing + } + + @Test + public void testAddKeyguardChangeListener_ensureCallback() { + mController.addKeyguardChangeListener(mKeyguardChangeListener); + + mController.onKeyguardVisibilityChanged(true, false, false); + assertTrue(mKeyguardChangeListener.visibilityChanged == 1); + assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0); + } + + @Test + public void testDoubleAddKeyguardChangeListener_ensureSingleCallback() { + mController.addKeyguardChangeListener(mKeyguardChangeListener); + mController.addKeyguardChangeListener(mKeyguardChangeListener); + + mController.onKeyguardVisibilityChanged(true, false, false); + assertTrue(mKeyguardChangeListener.visibilityChanged == 1); + assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0); + } + + @Test + public void testAddRemoveKeyguardChangeListener_ensureNoCallback() { + mController.addKeyguardChangeListener(mKeyguardChangeListener); + mController.removeKeyguardChangeListener(mKeyguardChangeListener); + + mController.onKeyguardVisibilityChanged(true, false, false); + assertTrue(mKeyguardChangeListener.visibilityChanged == 0); + assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0); + } + + @Test + public void testKeyguardVisibilityChanged() { + mController.addKeyguardChangeListener(mKeyguardChangeListener); + + mController.onKeyguardVisibilityChanged(true, true, true); + assertTrue(mKeyguardChangeListener.visibilityChanged == 1); + assertTrue(mKeyguardChangeListener.lastAnimatingDismiss); + assertTrue(mKeyguardChangeListener.lastOccluded); + assertTrue(mKeyguardChangeListener.lastAnimatingDismiss); + assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0); + } + + @Test + public void testKeyguardDismissAnimationFinished() { + mController.addKeyguardChangeListener(mKeyguardChangeListener); + + mController.onKeyguardDismissAnimationFinished(); + assertTrue(mKeyguardChangeListener.visibilityChanged == 0); + assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 1); + } + + @Test + public void testAddConfigurationChangeListener_ensureCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + newConfig.densityDpi = 200; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + } + + @Test + public void testDoubleAddConfigurationChangeListener_ensureSingleCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + mController.addConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + newConfig.densityDpi = 200; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + } + + @Test + public void testAddRemoveConfigurationChangeListener_ensureNoCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + mController.removeConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + newConfig.densityDpi = 200; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 0); + } + + @Test + public void testMultipleConfigurationChangeListeners() { + TestConfigurationChangeListener listener2 = new TestConfigurationChangeListener(); + mController.addConfigurationChangeListener(mConfigChangeListener); + mController.addConfigurationChangeListener(listener2); + + Configuration newConfig = getConfigurationCopy(); + newConfig.densityDpi = 200; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + assertTrue(listener2.configChanges == 1); + } + + @Test + public void testRemoveListenerDuringCallback() { + TestConfigurationChangeListener badListener = new TestConfigurationChangeListener() { + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + mController.removeConfigurationChangeListener(this); + } + }; + mController.addConfigurationChangeListener(badListener); + mController.addConfigurationChangeListener(mConfigChangeListener); + + // Ensure we don't fail just because a listener was removed mid-callback + Configuration newConfig = getConfigurationCopy(); + newConfig.densityDpi = 200; + mController.onConfigurationChanged(newConfig); + } + + @Test + public void testDensityChangeCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + newConfig.densityDpi = 200; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + assertTrue(mConfigChangeListener.densityChanges == 1); + assertTrue(mConfigChangeListener.smallestWidthChanges == 0); + assertTrue(mConfigChangeListener.themeChanges == 0); + assertTrue(mConfigChangeListener.localeChanges == 0); + } + + @Test + public void testFontScaleChangeCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + newConfig.fontScale = 2; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + assertTrue(mConfigChangeListener.densityChanges == 1); + assertTrue(mConfigChangeListener.smallestWidthChanges == 0); + assertTrue(mConfigChangeListener.themeChanges == 0); + assertTrue(mConfigChangeListener.localeChanges == 0); + } + + @Test + public void testSmallestWidthChangeCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + newConfig.smallestScreenWidthDp = 100; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + assertTrue(mConfigChangeListener.densityChanges == 0); + assertTrue(mConfigChangeListener.smallestWidthChanges == 1); + assertTrue(mConfigChangeListener.themeChanges == 0); + assertTrue(mConfigChangeListener.localeChanges == 0); + } + + @Test + public void testThemeChangeCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + newConfig.assetsSeq++; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + assertTrue(mConfigChangeListener.densityChanges == 0); + assertTrue(mConfigChangeListener.smallestWidthChanges == 0); + assertTrue(mConfigChangeListener.themeChanges == 1); + assertTrue(mConfigChangeListener.localeChanges == 0); + } + + @Test + public void testNightModeChangeCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + newConfig.uiMode = Configuration.UI_MODE_NIGHT_YES; + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + assertTrue(mConfigChangeListener.densityChanges == 0); + assertTrue(mConfigChangeListener.smallestWidthChanges == 0); + assertTrue(mConfigChangeListener.themeChanges == 1); + assertTrue(mConfigChangeListener.localeChanges == 0); + } + + @Test + public void testLocaleChangeCallback() { + mController.addConfigurationChangeListener(mConfigChangeListener); + + Configuration newConfig = getConfigurationCopy(); + // Just change the locales to be different + if (newConfig.locale == Locale.CANADA) { + newConfig.locale = Locale.US; + } else { + newConfig.locale = Locale.CANADA; + } + mController.onConfigurationChanged(newConfig); + assertTrue(mConfigChangeListener.configChanges == 1); + assertTrue(mConfigChangeListener.densityChanges == 0); + assertTrue(mConfigChangeListener.smallestWidthChanges == 0); + assertTrue(mConfigChangeListener.themeChanges == 0); + assertTrue(mConfigChangeListener.localeChanges == 1); + } + + private Configuration getConfigurationCopy() { + final Configuration c = new Configuration(InstrumentationRegistry.getInstrumentation() + .getTargetContext().getResources().getConfiguration()); + // In tests this might be undefined so make sure it's valid + c.assetsSeq = 1; + return c; + } + + private class TestConfigurationChangeListener implements ConfigurationChangeListener { + // Counts of number of times each of the callbacks are called + public int configChanges; + public int densityChanges; + public int smallestWidthChanges; + public int themeChanges; + public int localeChanges; + + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + configChanges++; + } + + @Override + public void onDensityOrFontScaleChanged() { + densityChanges++; + } + + @Override + public void onSmallestScreenWidthChanged() { + smallestWidthChanges++; + } + + @Override + public void onThemeChanged() { + themeChanges++; + } + + @Override + public void onLocaleOrLayoutDirectionChanged() { + localeChanges++; + } + } + + private class TestKeyguardChangeListener implements KeyguardChangeListener { + // Counts of number of times each of the callbacks are called + public int visibilityChanged; + public boolean lastVisibility; + public boolean lastOccluded; + public boolean lastAnimatingDismiss; + public int dismissAnimationFinished; + + @Override + public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, + boolean animatingDismiss) { + lastVisibility = visible; + lastOccluded = occluded; + lastAnimatingDismiss = animatingDismiss; + visibilityChanged++; + } + + @Override + public void onKeyguardDismissAnimationFinished() { + dismissAnimationFinished++; + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java deleted file mode 100644 index 7583418fc018..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2021 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.wm.shell.tasksurfacehelper; - -import static org.mockito.Mockito.verify; - -import android.testing.AndroidTestingRunner; -import android.view.SurfaceControl; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.ShellExecutor; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class TaskSurfaceHelperControllerTest extends ShellTestCase { - private TaskSurfaceHelperController mTaskSurfaceHelperController; - @Mock - private ShellTaskOrganizer mMockTaskOrganizer; - @Mock - private ShellExecutor mMockShellExecutor; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mTaskSurfaceHelperController = new TaskSurfaceHelperController( - mMockTaskOrganizer, mMockShellExecutor); - } - - @Test - public void testSetGameModeForTask() { - mTaskSurfaceHelperController.setGameModeForTask(/*taskId*/1, /*gameMode*/3); - verify(mMockTaskOrganizer).setSurfaceMetadata(1, SurfaceControl.METADATA_GAME_MODE, 3); - } -} diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java index f3e15084d730..2031929514f3 100644 --- a/location/java/android/location/SatellitePvt.java +++ b/location/java/android/location/SatellitePvt.java @@ -539,7 +539,7 @@ public final class SatellitePvt implements Parcelable { * * <p>This field is valid if {@link #hasIssueOfDataEphemeris()} is true. */ - @IntRange(from = 0, to = 255) + @IntRange(from = 0, to = 1023) public int getIssueOfDataEphemeris() { return mIssueOfDataEphemeris; } @@ -847,8 +847,8 @@ public final class SatellitePvt implements Parcelable { */ @NonNull public Builder setIssueOfDataEphemeris( - @IntRange(from = 0, to = 255) int issueOfDataEphemeris) { - Preconditions.checkArgumentInRange(issueOfDataEphemeris, 0, 255, + @IntRange(from = 0, to = 1023) int issueOfDataEphemeris) { + Preconditions.checkArgumentInRange(issueOfDataEphemeris, 0, 1023, "issueOfDataEphemeris"); mIssueOfDataEphemeris = issueOfDataEphemeris; mFlags = (byte) (mFlags | HAS_ISSUE_OF_DATA_EPHEMERIS); diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING index 4ec4767a35cc..05fbc7a52ec6 100644 --- a/media/TEST_MAPPING +++ b/media/TEST_MAPPING @@ -22,11 +22,15 @@ } ], "file_patterns": ["(?i)drm|crypto"] - } - ], - "imports": [ + }, { - "path": "frameworks/av/drm/mediadrm/plugins" + "name": "CtsMediaDrmFrameworkTestCases", + "options" : [ + { + "include-annotation": "android.platform.test.annotations.Presubmit" + } + ], + "file_patterns": ["(?i)drm|crypto"] } ] } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index bf9debf5ccce..bf6975714acd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -87,9 +87,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { if (DEBUG) { Log.d(TAG, "Bluetooth service connected"); } - mService = (BluetoothLeBroadcast) proxy; - mIsProfileReady = true; - registerServiceCallBack(mExecutor, mBroadcastCallback); + if(!mIsProfileReady) { + mService = (BluetoothLeBroadcast) proxy; + mIsProfileReady = true; + registerServiceCallBack(mExecutor, mBroadcastCallback); + } } @Override @@ -97,8 +99,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { if (DEBUG) { Log.d(TAG, "Bluetooth service disconnected"); } - mIsProfileReady = false; - unregisterServiceCallBack(mBroadcastCallback); + if(mIsProfileReady) { + mIsProfileReady = false; + unregisterServiceCallBack(mBroadcastCallback); + } } }; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index d6d73046bed3..281501e6043c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -22,6 +22,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; +import android.media.AudioManager; import android.media.RoutingSessionInfo; import android.os.Build; import android.text.TextUtils; @@ -83,6 +84,7 @@ public class LocalMediaManager implements BluetoothCallback { private InfoMediaManager mInfoMediaManager; private String mPackageName; private MediaDevice mOnTransferBluetoothDevice; + private AudioManager mAudioManager; @VisibleForTesting List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); @@ -126,6 +128,7 @@ public class LocalMediaManager implements BluetoothCallback { mPackageName = packageName; mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null); + mAudioManager = context.getSystemService(AudioManager.class); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mLocalBluetoothManager == null) { Log.e(TAG, "Bluetooth is not supported on this device"); @@ -148,6 +151,7 @@ public class LocalMediaManager implements BluetoothCallback { mInfoMediaManager = infoMediaManager; mPackageName = packageName; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mAudioManager = context.getSystemService(AudioManager.class); } /** @@ -527,13 +531,16 @@ public class LocalMediaManager implements BluetoothCallback { synchronized (mMediaDevicesLock) { mMediaDevices.clear(); mMediaDevices.addAll(devices); - // Add disconnected bluetooth devices only when phone output device is available. + // Add muting expected bluetooth devices only when phone output device is available. for (MediaDevice device : devices) { final int type = device.getDeviceType(); if (type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE || type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE) { - mMediaDevices.addAll(buildDisconnectedBluetoothDevice()); + MediaDevice mutingExpectedDevice = getMutingExpectedDevice(); + if (mutingExpectedDevice != null) { + mMediaDevices.add(mutingExpectedDevice); + } break; } } @@ -552,6 +559,34 @@ public class LocalMediaManager implements BluetoothCallback { } } + private MediaDevice getMutingExpectedDevice() { + if (mBluetoothAdapter == null + || mAudioManager.getMutingExpectedDevice() == null) { + Log.w(TAG, "BluetoothAdapter is null or muting expected device not exist"); + return null; + } + final List<BluetoothDevice> bluetoothDevices = + mBluetoothAdapter.getMostRecentlyConnectedDevices(); + final CachedBluetoothDeviceManager cachedDeviceManager = + mLocalBluetoothManager.getCachedDeviceManager(); + for (BluetoothDevice device : bluetoothDevices) { + final CachedBluetoothDevice cachedDevice = + cachedDeviceManager.findDevice(device); + if (isBondedMediaDevice(cachedDevice) && isMutingExpectedDevice(cachedDevice)) { + return new BluetoothMediaDevice(mContext, + cachedDevice, + null, null, mPackageName); + } + } + return null; + } + + private boolean isMutingExpectedDevice(CachedBluetoothDevice cachedDevice) { + return mAudioManager.getMutingExpectedDevice() != null + && cachedDevice.getAddress().equals( + mAudioManager.getMutingExpectedDevice().getAddress()); + } + private List<MediaDevice> buildDisconnectedBluetoothDevice() { if (mBluetoothAdapter == null) { Log.w(TAG, "buildDisconnectedBluetoothDevice() BluetoothAdapter is null"); @@ -595,6 +630,13 @@ public class LocalMediaManager implements BluetoothCallback { return new ArrayList<>(mDisconnectedMediaDevices); } + private boolean isBondedMediaDevice(CachedBluetoothDevice cachedDevice) { + return cachedDevice != null + && cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED + && !cachedDevice.isConnected() + && isMediaDevice(cachedDevice); + } + private boolean isMediaDevice(CachedBluetoothDevice device) { for (LocalBluetoothProfile profile : device.getConnectableProfiles()) { if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile || diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index fa87de2b1353..ffd6b522e394 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -110,6 +110,7 @@ android_library { "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-common-java8", "androidx.lifecycle_lifecycle-extensions", + "androidx.lifecycle_lifecycle-runtime-ktx", "androidx.dynamicanimation_dynamicanimation", "androidx-constraintlayout_constraintlayout", "androidx.exifinterface_exifinterface", @@ -218,6 +219,7 @@ android_library { "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-common-java8", "androidx.lifecycle_lifecycle-extensions", + "androidx.lifecycle_lifecycle-runtime-ktx", "androidx.dynamicanimation_dynamicanimation", "androidx-constraintlayout_constraintlayout", "androidx.exifinterface_exifinterface", diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index f9a9ef65cab6..154a6fca9d09 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -102,7 +102,7 @@ // // If you don't use @Staging or @Postsubmit, your new test will immediately // block presubmit, which is probably not what you want! - "platinum-postsubmit": [ + "sysui-platinum-postsubmit": [ { "name": "PlatformScenarioTests", "options": [ @@ -121,7 +121,7 @@ ] } ], - "staged-platinum-postsubmit": [ + "sysui-staged-platinum-postsubmit": [ { "name": "PlatformScenarioTests", "options": [ diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index fbe33565f11d..8ddd430dadbc 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -523,9 +523,10 @@ class ActivityLaunchAnimator( state: LaunchAnimator.State, linearProgress: Float, ) { - if (transactionApplierView.viewRootImpl == null) { - // If the view root we synchronize with was detached, don't apply any transaction - // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw). + if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) { + // Don't apply any transaction if the view root we synchronize with was detached or + // if the SurfaceControl associated with [window] is not valid, as + // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw. return } @@ -605,9 +606,10 @@ class ActivityLaunchAnimator( state: LaunchAnimator.State, linearProgress: Float ) { - if (transactionApplierView.viewRootImpl == null) { - // If the view root we synchronize with was detached, don't apply any transaction - // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw). + if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) { + // Don't apply any transaction if the view root we synchronize with was detached or + // if the SurfaceControl associated with [navigationBar] is not valid, as + // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw. return } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 47f448d503c6..eb000ad312d7 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -49,17 +49,16 @@ private const val TAG = "GhostedViewLaunchAnimatorController" * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView] * whenever possible instead. */ -open class GhostedViewLaunchAnimatorController( +open class GhostedViewLaunchAnimatorController @JvmOverloads constructor( /** The view that will be ghosted and from which the background will be extracted. */ private val ghostedView: View, /** The [InteractionJankMonitor.CujType] associated to this animation. */ private val cujType: Int? = null, - private var interactionJankMonitor: InteractionJankMonitor? = null + private var interactionJankMonitor: InteractionJankMonitor = + InteractionJankMonitor.getInstance(), ) : ActivityLaunchAnimator.Controller { - constructor(view: View, type: Int) : this(view, type, null) - /** The container to which we will add the ghost view and expanding background. */ override var launchContainer = ghostedView.rootView as ViewGroup private val launchContainerOverlay: ViewGroupOverlay @@ -203,7 +202,7 @@ open class GhostedViewLaunchAnimatorController( val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX matrix.getValues(initialGhostViewMatrixValues) - cujType?.let { interactionJankMonitor?.begin(ghostedView, it) } + cujType?.let { interactionJankMonitor.begin(ghostedView, it) } } override fun onLaunchAnimationProgress( @@ -289,7 +288,7 @@ open class GhostedViewLaunchAnimatorController( return } - cujType?.let { interactionJankMonitor?.end(it) } + cujType?.let { interactionJankMonitor.end(it) } backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha diff --git a/packages/SystemUI/docs/user-file-manager.md b/packages/SystemUI/docs/user-file-manager.md index 64f1694af50a..52fa2066fbe1 100644 --- a/packages/SystemUI/docs/user-file-manager.md +++ b/packages/SystemUI/docs/user-file-manager.md @@ -1,10 +1,30 @@ # UserFileManager -This class is used to generate file paths and SharedPreferences that is compatible for multiple +This class is used to generate file paths and SharedPreferences that is compatible for specific OS users in SystemUI. Due to constraints in SystemUI, we can only read/write files as the system user. Therefore, for secondary users, we want to store secondary user specific files into the system user directory. + +## Usages + +Inject UserFileManager into your class. + +### fun getFile(fileName: String, userId: Int): File +Add a file name and user id. You can retrieve the current user id from UserTracker. This will +return a java.io File object that contains the file path to write/read to. + +i.e. `fileManager.getFile("example.xml", userTracker.userId)` + +### fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences +Add a file name, user id, and PreferencesMode. You can retrieve the current user id from +UserTracker. This returns SharedPreferences object that is tied to the specific user. Note that if +the SharedPreferences file does not exist, one will be created automatically. See +[SharedPreferences documentation](https://developer.android.com/reference/android/content/Context#getSharedPreferences(java.lang.String,%20int)) +for more details. + +i.e. `fileManager.getSharedPreferences("prefs.xml", userTracker.userId, 0)` + ## Handling User Removal This class will listen for Intent.ACTION_USER_REMOVED and remove directories that no longer diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java index 3058d9466121..bef61b867f7d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java @@ -23,7 +23,9 @@ import java.util.TimeZone; /** * Plugin used to replace main clock in keyguard. + * @deprecated Migrating to ClockProviderPlugin */ +@Deprecated @ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION) public interface ClockPlugin extends Plugin { diff --git a/packages/SystemUI/res/drawable/dream_overlay_camera_off.xml b/packages/SystemUI/res/drawable/dream_overlay_camera_off.xml new file mode 100644 index 000000000000..159655e39d24 --- /dev/null +++ b/packages/SystemUI/res/drawable/dream_overlay_camera_off.xml @@ -0,0 +1,29 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="56dp" + android:height="24dp" + android:viewportWidth="56" + android:viewportHeight="24"> + <path + android:pathData="M12,0L44,0A12,12 0,0 1,56 12L56,12A12,12 0,0 1,44 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z" + android:fillColor="#5F6368"/> + <path + android:pathData="M21.872,5.873L20.926,6.813L21.492,7.38C21.392,7.566 21.332,7.773 21.332,8V16C21.332,16.733 21.932,17.333 22.666,17.333H30.666C30.892,17.333 31.099,17.273 31.286,17.173L33.186,19.073L34.126,18.133L31.999,16L21.872,5.873ZM31.999,10.986V8C31.999,7.266 31.399,6.666 30.666,6.666H24.552L25.886,8H30.666V12.78L31.999,14.113V13.013L34.666,15.666V8.333L31.999,10.986ZM22.666,8.553V16H30.112L22.666,8.553Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> +</vector> diff --git a/packages/SystemUI/res/drawable/dream_overlay_mic_and_camera_off.xml b/packages/SystemUI/res/drawable/dream_overlay_mic_and_camera_off.xml new file mode 100644 index 000000000000..087dde78833f --- /dev/null +++ b/packages/SystemUI/res/drawable/dream_overlay_mic_and_camera_off.xml @@ -0,0 +1,33 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="56dp" + android:height="24dp" + android:viewportWidth="56" + android:viewportHeight="24"> + <path + android:pathData="M12,0L44,0A12,12 0,0 1,56 12L56,12A12,12 0,0 1,44 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z" + android:fillColor="#5F6368"/> + <path + android:pathData="M15.332,7.333C15.332,6.966 15.632,6.666 15.999,6.666C16.366,6.666 16.666,6.966 16.666,7.333V10.78L17.879,11.993C17.952,11.786 17.999,11.566 17.999,11.333V7.333C17.999,6.226 17.106,5.333 15.999,5.333C14.892,5.333 13.999,6.226 13.999,7.333V8.113L15.332,9.446V7.333ZM9.872,5.873L8.926,6.813L16.692,14.58C16.472,14.633 16.239,14.666 15.999,14.666C14.159,14.666 12.666,13.173 12.666,11.333H11.332C11.332,13.686 13.072,15.62 15.332,15.946V18H16.666V15.946C17.046,15.893 17.412,15.786 17.759,15.64L21.186,19.066L22.126,18.126L9.872,5.873ZM19.332,11.333H20.666C20.666,12.313 20.359,13.213 19.846,13.96L18.872,12.986C19.159,12.5 19.332,11.94 19.332,11.333Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> + <path + android:pathData="M33.872,5.873L32.926,6.813L33.492,7.38C33.392,7.566 33.332,7.773 33.332,8V16C33.332,16.733 33.932,17.333 34.666,17.333H42.666C42.892,17.333 43.099,17.273 43.286,17.173L45.186,19.073L46.126,18.133L43.999,16L33.872,5.873ZM43.999,10.986V8C43.999,7.266 43.399,6.666 42.666,6.666H36.552L37.886,8H42.666V12.78L43.999,14.113V13.013L46.666,15.666V8.333L43.999,10.986ZM34.666,8.553V16H42.112L34.666,8.553Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> +</vector> diff --git a/packages/SystemUI/res/drawable/dream_overlay_mic_off.xml b/packages/SystemUI/res/drawable/dream_overlay_mic_off.xml new file mode 100644 index 000000000000..693250d39f95 --- /dev/null +++ b/packages/SystemUI/res/drawable/dream_overlay_mic_off.xml @@ -0,0 +1,29 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="56dp" + android:height="24dp" + android:viewportWidth="56" + android:viewportHeight="24"> + <path + android:pathData="M12,0L44,0A12,12 0,0 1,56 12L56,12A12,12 0,0 1,44 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z" + android:fillColor="#5F6368"/> + <path + android:pathData="M27.807,7.133C27.807,6.767 28.107,6.467 28.473,6.467C28.84,6.467 29.14,6.767 29.14,7.133V10.58L30.353,11.793C30.427,11.587 30.473,11.367 30.473,11.133V7.133C30.473,6.027 29.58,5.133 28.473,5.133C27.367,5.133 26.473,6.027 26.473,7.133V7.913L27.807,9.247V7.133ZM22.347,5.673L21.4,6.613L29.167,14.38C28.947,14.433 28.713,14.467 28.473,14.467C26.633,14.467 25.14,12.973 25.14,11.133H23.807C23.807,13.487 25.547,15.42 27.807,15.747V17.8H29.14V15.747C29.52,15.693 29.887,15.587 30.233,15.44L33.66,18.867L34.6,17.927L22.347,5.673ZM31.807,11.133H33.14C33.14,12.113 32.833,13.013 32.32,13.76L31.347,12.787C31.633,12.3 31.807,11.74 31.807,11.133Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_media_pause_container.xml b/packages/SystemUI/res/drawable/ic_media_pause_container.xml index b92e63575b95..ea9eb8cd5475 100644 --- a/packages/SystemUI/res/drawable/ic_media_pause_container.xml +++ b/packages/SystemUI/res/drawable/ic_media_pause_container.xml @@ -22,11 +22,6 @@ android:viewportHeight="48"
android:viewportWidth="48">
<group android:name="_R_G">
- <group android:name="_R_G_L_1_G"
- android:translateX="24"
- android:translateY="24"
- android:scaleX="0.5"
- android:scaleY="0.5"/>
<group android:name="_R_G_L_0_G"
android:translateX="24"
android:translateY="24"
@@ -46,7 +41,7 @@ <aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
- android:duration="500"
+ android:duration="250"
android:startOffset="0"
android:valueFrom="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "
android:valueTo="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "
@@ -62,7 +57,7 @@ <aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="translateX"
- android:duration="517"
+ android:duration="267"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
@@ -70,4 +65,4 @@ </set>
</aapt:attr>
</target>
-</animated-vector>
\ No newline at end of file +</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_play_container.xml b/packages/SystemUI/res/drawable/ic_media_play_container.xml index 2fc9fc88a4fd..4cb011a89f35 100644 --- a/packages/SystemUI/res/drawable/ic_media_play_container.xml +++ b/packages/SystemUI/res/drawable/ic_media_play_container.xml @@ -41,7 +41,7 @@ <aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
- android:duration="500"
+ android:duration="250"
android:startOffset="0"
android:valueFrom="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "
android:valueTo="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "
@@ -57,7 +57,7 @@ <aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="translateX"
- android:duration="517"
+ android:duration="267"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
@@ -65,4 +65,4 @@ </set>
</aapt:attr>
</target>
-</animated-vector>
\ No newline at end of file +</animated-vector>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 99a5a2e904f6..1a1fc75a41a1 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -115,6 +115,7 @@ android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font" android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font" android:textColor="?attr/overlayButtonTextColor" + android:textColorLink="?attr/overlayButtonTextColor" android:background="?androidprv:attr/colorAccentSecondary" android:layout_width="@dimen/clipboard_preview_size" android:layout_height="@dimen/clipboard_preview_size"/> diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index d0f4903a3421..70a770912c7f 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -70,15 +70,32 @@ android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_wifi_off" /> - <com.android.systemui.dreams.DreamOverlayDotImageView + <ImageView + android:id="@+id/dream_overlay_mic_off" + android:layout_width="@dimen/dream_overlay_grey_chip_width" + android:layout_height="match_parent" + android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:src="@drawable/dream_overlay_mic_off" + android:visibility="gone" + android:contentDescription="@string/dream_overlay_status_bar_mic_off" /> + + <ImageView + android:id="@+id/dream_overlay_camera_off" + android:layout_width="@dimen/dream_overlay_grey_chip_width" + android:layout_height="match_parent" + android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:src="@drawable/dream_overlay_camera_off" + android:visibility="gone" + android:contentDescription="@string/dream_overlay_status_bar_camera_off" /> + + <ImageView android:id="@+id/dream_overlay_camera_mic_off" - android:layout_width="@dimen/dream_overlay_camera_mic_off_indicator_size" - android:layout_height="@dimen/dream_overlay_camera_mic_off_indicator_size" - android:layout_gravity="center_vertical" + android:layout_width="@dimen/dream_overlay_grey_chip_width" + android:layout_height="match_parent" android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:src="@drawable/dream_overlay_mic_and_camera_off" android:visibility="gone" - android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" - app:dotColor="@color/dream_overlay_camera_mic_off_dot_color" /> + android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" /> </LinearLayout> </com.android.systemui.dreams.DreamOverlayStatusBarView> diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml index e47eed9ea04a..d27fa192e741 100644 --- a/packages/SystemUI/res/layout/keyguard_status_bar.xml +++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml @@ -60,9 +60,8 @@ </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer> <FrameLayout android:id="@+id/system_icons_container" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_weight="1" android:layout_marginEnd="@dimen/status_bar_padding_end" android:gravity="center_vertical|end"> <include layout="@layout/system_icons" /> diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml index fa696cc1f54c..aadfae8c5aed 100644 --- a/packages/SystemUI/res/layout/notification_icon_area.xml +++ b/packages/SystemUI/res/layout/notification_icon_area.xml @@ -14,18 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<com.android.keyguard.AlphaOptimizedLinearLayout +<com.android.systemui.statusbar.phone.NotificationIconContainer xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/notification_icon_area_inner" - android:layout_width="match_parent" + android:id="@+id/notificationIcons" + android:layout_width="wrap_content" android:layout_height="match_parent" - android:clipChildren="false"> - <com.android.systemui.statusbar.phone.NotificationIconContainer - android:id="@+id/notificationIcons" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignParentStart="true" - android:gravity="center_vertical" - android:orientation="horizontal" - android:clipChildren="false"/> -</com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file + android:clipChildren="false"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml index 7102375a89bf..f45cc7c464d5 100644 --- a/packages/SystemUI/res/layout/people_space_activity.xml +++ b/packages/SystemUI/res/layout/people_space_activity.xml @@ -13,103 +13,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<LinearLayout +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/top_level" + android:id="@+id/container" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="8dp"> - <TextView - android:id="@+id/select_conversation_title" - android:text="@string/select_conversation_title" - android:gravity="center" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="24sp"/> - - <TextView - android:id="@+id/select_conversation" - android:text="@string/select_conversation_text" - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="16sp" - android:paddingVertical="24dp" - android:paddingHorizontal="48dp"/> - - <androidx.core.widget.NestedScrollView - android:id="@+id/scroll_view" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <LinearLayout - android:id="@+id/scroll_layout" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:orientation="vertical"> - - <LinearLayout - android:id="@+id/priority" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="35dp"> - <TextView - android:id="@+id/priority_header" - android:text="@string/priority_conversations" - android:layout_width="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:textColor="?androidprv:attr/colorAccentPrimaryVariant" - android:textSize="14sp" - android:paddingStart="16dp" - android:layout_height="wrap_content"/> - - <LinearLayout - android:id="@+id/priority_tiles" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="10dp" - android:orientation="vertical" - android:background="@drawable/rounded_bg_full_large_radius" - android:clipToOutline="true"> - </LinearLayout> - </LinearLayout> - - <LinearLayout - android:id="@+id/recent" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TextView - android:id="@+id/recent_header" - android:gravity="start" - android:text="@string/recent_conversations" - android:layout_width="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:textColor="?androidprv:attr/colorAccentPrimaryVariant" - android:textSize="14sp" - android:paddingStart="16dp" - android:layout_height="wrap_content"/> - - <LinearLayout - android:id="@+id/recent_tiles" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="10dp" - android:orientation="vertical" - android:background="@drawable/rounded_bg_full_large_radius" - android:clipToOutline="true"> - </LinearLayout> - </LinearLayout> - </LinearLayout> - </androidx.core.widget.NestedScrollView> -</LinearLayout>
\ No newline at end of file + android:layout_height="match_parent"> + <!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at + runtime depending on the number of conversations to show. --> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml index 2e9ff07caed9..e929169cfe3d 100644 --- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml +++ b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml @@ -16,7 +16,7 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/top_level" + android:id="@+id/top_level_no_conversations" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="24dp" diff --git a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml new file mode 100644 index 000000000000..2384963c44db --- /dev/null +++ b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml @@ -0,0 +1,115 @@ +<!-- + ~ 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" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@+id/top_level_with_conversations" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="8dp"> + <TextView + android:id="@+id/select_conversation_title" + android:text="@string/select_conversation_title" + android:gravity="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textColor="?android:attr/textColorPrimary" + android:textSize="24sp"/> + + <TextView + android:id="@+id/select_conversation" + android:text="@string/select_conversation_text" + android:gravity="center" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textColor="?android:attr/textColorPrimary" + android:textSize="16sp" + android:paddingVertical="24dp" + android:paddingHorizontal="48dp"/> + + <androidx.core.widget.NestedScrollView + android:id="@+id/scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:id="@+id/scroll_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/priority" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="35dp"> + <TextView + android:id="@+id/priority_header" + android:text="@string/priority_conversations" + android:layout_width="wrap_content" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" + android:textColor="?androidprv:attr/colorAccentPrimaryVariant" + android:textSize="14sp" + android:paddingStart="16dp" + android:layout_height="wrap_content"/> + + <LinearLayout + android:id="@+id/priority_tiles" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:orientation="vertical" + android:background="@drawable/rounded_bg_full_large_radius" + android:clipToOutline="true"> + </LinearLayout> + </LinearLayout> + + <LinearLayout + android:id="@+id/recent" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView + android:id="@+id/recent_header" + android:gravity="start" + android:text="@string/recent_conversations" + android:layout_width="wrap_content" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" + android:textColor="?androidprv:attr/colorAccentPrimaryVariant" + android:textSize="14sp" + android:paddingStart="16dp" + android:layout_height="wrap_content"/> + + <LinearLayout + android:id="@+id/recent_tiles" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:orientation="vertical" + android:background="@drawable/rounded_bg_full_large_radius" + android:clipToOutline="true"> + </LinearLayout> + </LinearLayout> + </LinearLayout> + </androidx.core.widget.NestedScrollView> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml index 2a2c35dde841..b0599caae6df 100644 --- a/packages/SystemUI/res/layout/people_space_tile_view.xml +++ b/packages/SystemUI/res/layout/people_space_tile_view.xml @@ -37,8 +37,8 @@ <ImageView android:id="@+id/tile_view_person_icon" - android:layout_width="52dp" - android:layout_height="52dp" /> + android:layout_width="@dimen/avatar_size_for_medium" + android:layout_height="@dimen/avatar_size_for_medium" /> <LinearLayout android:orientation="horizontal" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index deab1ebd6507..e281511140c7 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -47,52 +47,63 @@ android:paddingStart="@dimen/status_bar_padding_start" android:paddingEnd="@dimen/status_bar_padding_end" android:paddingTop="@dimen/status_bar_padding_top" - android:orientation="horizontal" - > + android:orientation="horizontal"> + + <!-- Container for the entire start half of the status bar. It will always use the same + width, independent of the number of visible children and sub-children. --> <FrameLayout + android:id="@+id/status_bar_start_side_container" android:layout_height="match_parent" android:layout_width="0dp" android:layout_weight="1"> - <include layout="@layout/heads_up_status_bar_layout" /> + <!-- Container that is wrapped around the views on the start half of the status bar. + Its width will change with the number of visible children and sub-children. + It is useful when we want to know the visible bounds of the content. --> + <FrameLayout + android:id="@+id/status_bar_start_side_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:clipChildren="false"> - <!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the - individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and - DISABLE_NOTIFICATION_ICONS, respectively --> - <LinearLayout - android:id="@+id/status_bar_left_side" - android:layout_height="match_parent" - android:layout_width="match_parent" - android:clipChildren="false" - > - <ViewStub - android:id="@+id/operator_name" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout="@layout/operator_name" /> + <include layout="@layout/heads_up_status_bar_layout" /> - <com.android.systemui.statusbar.policy.Clock - android:id="@+id/clock" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:textAppearance="@style/TextAppearance.StatusBar.Clock" - android:singleLine="true" - android:paddingStart="@dimen/status_bar_left_clock_starting_padding" - android:paddingEnd="@dimen/status_bar_left_clock_end_padding" - android:gravity="center_vertical|start" - /> - - <include layout="@layout/ongoing_call_chip" /> - - <com.android.systemui.statusbar.AlphaOptimizedFrameLayout - android:id="@+id/notification_icon_area" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="horizontal" - android:clipChildren="false"/> + <!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the + individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK + and DISABLE_NOTIFICATION_ICONS, respectively --> + <LinearLayout + android:id="@+id/status_bar_start_side_except_heads_up" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:clipChildren="false"> + <ViewStub + android:id="@+id/operator_name" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout="@layout/operator_name" /> + + <com.android.systemui.statusbar.policy.Clock + android:id="@+id/clock" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" + android:singleLine="true" + android:paddingStart="@dimen/status_bar_left_clock_starting_padding" + android:paddingEnd="@dimen/status_bar_left_clock_end_padding" + android:gravity="center_vertical|start" + /> + + <include layout="@layout/ongoing_call_chip" /> - </LinearLayout> + <com.android.systemui.statusbar.AlphaOptimizedFrameLayout + android:id="@+id/notification_icon_area" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipChildren="false"/> + + </LinearLayout> + </FrameLayout> </FrameLayout> <!-- Space should cover the notch (if it exists) and let other views lay out around it --> @@ -103,42 +114,57 @@ android:gravity="center_horizontal|center_vertical" /> - <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area" + <!-- Container for the entire end half of the status bar. It will always use the same + width, independent of the number of visible children and sub-children. --> + <FrameLayout + android:id="@+id/status_bar_end_side_container" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" - android:orientation="horizontal" - android:gravity="center_vertical|end" - > - - <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer - android:id="@+id/user_switcher_container" + android:clipChildren="false"> + + <!-- Container that is wrapped around the views on the end half of the + status bar. Its width will change with the number of visible children and + sub-children. + It is useful when we want know the visible bounds of the content.--> + <com.android.keyguard.AlphaOptimizedLinearLayout + android:id="@+id/status_bar_end_side_content" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" + android:layout_height="match_parent" + android:layout_gravity="end" android:orientation="horizontal" - android:paddingTop="4dp" - android:paddingBottom="4dp" - android:paddingStart="8dp" - android:paddingEnd="8dp" - android:layout_marginEnd="16dp" - android:background="@drawable/status_bar_user_chip_bg" - android:visibility="visible" > - <ImageView android:id="@+id/current_user_avatar" - android:layout_width="@dimen/multi_user_avatar_keyguard_size" - android:layout_height="@dimen/multi_user_avatar_keyguard_size" - android:scaleType="centerInside" - android:paddingEnd="4dp" /> - - <TextView android:id="@+id/current_user_name" + android:gravity="center_vertical|end" + android:clipChildren="false"> + + <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer + android:id="@+id/user_switcher_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.StatusBar.Clock" - /> - </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer> - - <include layout="@layout/system_icons" /> - </com.android.keyguard.AlphaOptimizedLinearLayout> + android:gravity="center" + android:orientation="horizontal" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:layout_marginEnd="16dp" + android:background="@drawable/status_bar_user_chip_bg" + android:visibility="visible" > + <ImageView android:id="@+id/current_user_avatar" + android:layout_width="@dimen/multi_user_avatar_keyguard_size" + android:layout_height="@dimen/multi_user_avatar_keyguard_size" + android:scaleType="centerInside" + android:paddingEnd="4dp" /> + + <TextView android:id="@+id/current_user_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" + /> + </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer> + + <include layout="@layout/system_icons" /> + </com.android.keyguard.AlphaOptimizedLinearLayout> + </FrameLayout> </LinearLayout> <ViewStub diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 4d5bf53eb64a..6423a50fc107 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -18,7 +18,7 @@ --> -<com.android.systemui.statusbar.phone.NotificationPanelView +<com.android.systemui.shade.NotificationPanelView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/notification_panel" @@ -67,7 +67,7 @@ </com.android.keyguard.LockIconView> - <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer + <com.android.systemui.shade.NotificationsQuickSettingsContainer android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="@integer/notification_panel_layout_gravity" @@ -150,11 +150,11 @@ android:text="@string/tap_again" android:visibility="gone" /> - </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer> + </com.android.systemui.shade.NotificationsQuickSettingsContainer> <FrameLayout android:id="@+id/preview_container" android:layout_width="match_parent" android:layout_height="match_parent"> </FrameLayout> -</com.android.systemui.statusbar.phone.NotificationPanelView> +</com.android.systemui.shade.NotificationPanelView> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 60860bad9c64..86f8ce26ccab 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -18,7 +18,7 @@ --> <!-- This is the notification shade window. --> -<com.android.systemui.statusbar.phone.NotificationShadeWindowView +<com.android.systemui.shade.NotificationShadeWindowView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sysui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" @@ -114,4 +114,4 @@ android:importantForAccessibility="no" sysui:ignoreRightInset="true" /> -</com.android.systemui.statusbar.phone.NotificationShadeWindowView> +</com.android.systemui.shade.NotificationShadeWindowView> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index bb5c5928181a..022a6b2f4da2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1449,6 +1449,7 @@ @*android:dimen/status_bar_system_icon_size</dimen> <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen> <dimen name="dream_overlay_notification_indicator_size">6dp</dimen> + <dimen name="dream_overlay_grey_chip_width">56dp</dimen> <!-- Dream overlay complications related dimensions --> <dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index cdb4f442ee78..9c2542cbd05f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2297,8 +2297,8 @@ <string name="media_output_dialog_disconnected">(disconnected)</string> <!-- Summary for connecting error message [CHAR LIMIT=NONE] --> <string name="media_output_dialog_connect_failed">Can\'t switch. Tap to try again.</string> - <!-- Title for pairing item [CHAR LIMIT=60] --> - <string name="media_output_dialog_pairing_new">Pair new device</string> + <!-- Title for connecting item [CHAR LIMIT=60] --> + <string name="media_output_dialog_pairing_new">Connect a device</string> <!-- Title for launch app [CHAR LIMIT=60] --> <string name="media_output_dialog_launch_app_text">To cast this session, please open the app.</string> <!-- App name when can't get app name [CHAR LIMIT=60] --> @@ -2555,6 +2555,10 @@ <string name="dream_overlay_status_bar_priority_mode">Priority mode</string> <!-- Content description for the alarm set icon in the dream overlay status bar [CHAR LIMIT=NONE] --> <string name="dream_overlay_status_bar_alarm_set">Alarm set</string> + <!-- Content description for the camera off icon in the dream overlay status bar [CHAR LIMIT=NONE] --> + <string name="dream_overlay_status_bar_camera_off">Camera is off</string> + <!-- Content description for the mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] --> + <string name="dream_overlay_status_bar_mic_off">Mic is off</string> <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] --> <string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string> <!-- Content description for the notifications indicator icon in the dream overlay status bar [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp index 601e92fe20ea..f449398fc9f8 100644 --- a/packages/SystemUI/screenshot/Android.bp +++ b/packages/SystemUI/screenshot/Android.bp @@ -38,6 +38,7 @@ android_library { "androidx.test.espresso.core", "androidx.appcompat_appcompat", "platform-screenshot-diff-core", + "guava", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml index 40e50bbb6bbf..a7f8a264e892 100644 --- a/packages/SystemUI/screenshot/res/values/themes.xml +++ b/packages/SystemUI/screenshot/res/values/themes.xml @@ -19,6 +19,12 @@ <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> + <!-- We make the status and navigation bars transparent so that the screenshotted content is + not clipped by the status bar height when drawn into the Bitmap (which is what happens + given that we draw the view into the Bitmap using hardware acceleration). --> + <item name="android:statusBarColor">@android:color/transparent</item> + <item name="android:navigationBarColor">@android:color/transparent</item> + <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests --> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> </style> diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt index 3d26cdab891d..a4a70a49fce3 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt @@ -24,6 +24,8 @@ import platform.test.screenshot.matchers.MSSIMMatcher import platform.test.screenshot.matchers.PixelPerfectMatcher /** Draw this [View] into a [Bitmap]. */ +// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their +// tests. fun View.drawIntoBitmap(): Bitmap { val bitmap = Bitmap.createBitmap( 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 new file mode 100644 index 000000000000..c609e6f8b4bf --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt @@ -0,0 +1,180 @@ +package com.android.systemui.testing.screenshot + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Rect +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.PixelCopy +import android.view.SurfaceView +import android.view.View +import android.view.ViewTreeObserver +import android.view.Window +import androidx.annotation.RequiresApi +import androidx.concurrent.futures.ResolvableFuture +import androidx.test.annotation.ExperimentalTestApi +import androidx.test.core.internal.os.HandlerExecutor +import androidx.test.platform.graphics.HardwareRendererCompat +import com.google.common.util.concurrent.ListenableFuture + +/* + * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to + * [View.captureToBitmap]. + * TODO(b/195673633): Remove this fork and use the AndroidX version instead. + */ + +/** + * Asynchronously captures an image of the underlying view into a [Bitmap]. + * + * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the + * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used. + * + * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required. + * + * This API is primarily intended for use in lower layer libraries or frameworks. For test authors, + * its recommended to use espresso or compose's captureToImage. + * + * This API is currently experimental and subject to change or removal. + */ +@ExperimentalTestApi +@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) +fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> { + val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create() + val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper())) + + // disable drawing again if necessary once work is complete + if (!HardwareRendererCompat.isDrawingEnabled()) { + HardwareRendererCompat.setDrawingEnabled(true) + bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor) + } + + mainExecutor.execute { + val forceRedrawFuture = forceRedraw() + forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor) + } + + return bitmapFuture +} + +/** + * Trigger a redraw of the given view. + * + * Should only be called on UI thread. + * + * @return a [ListenableFuture] that will be complete once ui drawing is complete + */ +// NoClassDefFoundError occurs on API 15 +@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) +// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +@ExperimentalTestApi +fun View.forceRedraw(): ListenableFuture<Void> { + val future: ResolvableFuture<Void> = ResolvableFuture.create() + + if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) { + viewTreeObserver.registerFrameCommitCallback() { future.set(null) } + } else { + viewTreeObserver.addOnDrawListener( + object : ViewTreeObserver.OnDrawListener { + var handled = false + override fun onDraw() { + if (!handled) { + handled = true + future.set(null) + // cannot remove on draw listener inside of onDraw + Handler(Looper.getMainLooper()).post { + viewTreeObserver.removeOnDrawListener(this) + } + } + } + } + ) + } + invalidate() + return future +} + +private fun View.generateBitmap( + bitmapFuture: ResolvableFuture<Bitmap>, + window: Window? = null, +) { + if (bitmapFuture.isCancelled) { + return + } + val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + when { + Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture) + this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture) + else -> { + val window = window ?: getActivity()?.window + if (window != null) { + generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture) + } else { + Log.i( + "View.captureToImage", + "Could not find window for view. Falling back to View#draw instead of PixelCopy" + ) + generateBitmapFromDraw(destBitmap, bitmapFuture) + } + } + } +} + +@SuppressWarnings("NewApi") +private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy( + destBitmap: Bitmap, + bitmapFuture: ResolvableFuture<Bitmap> +) { + val onCopyFinished = + PixelCopy.OnPixelCopyFinishedListener { result -> + if (result == PixelCopy.SUCCESS) { + bitmapFuture.set(destBitmap) + } else { + bitmapFuture.setException( + RuntimeException(String.format("PixelCopy failed: %d", result)) + ) + } + } + PixelCopy.request(this, null, destBitmap, onCopyFinished, handler) +} + +internal fun View.generateBitmapFromDraw( + destBitmap: Bitmap, + bitmapFuture: ResolvableFuture<Bitmap> +) { + destBitmap.density = resources.displayMetrics.densityDpi + computeScroll() + val canvas = Canvas(destBitmap) + canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat()) + draw(canvas) + bitmapFuture.set(destBitmap) +} + +private fun View.getActivity(): Activity? { + fun Context.getActivity(): Activity? { + return when (this) { + is Activity -> this + is ContextWrapper -> this.baseContext.getActivity() + else -> null + } + } + return context.getActivity() +} + +private fun View.generateBitmapFromPixelCopy( + window: Window, + destBitmap: Bitmap, + bitmapFuture: ResolvableFuture<Bitmap> +) { + val locationInWindow = intArrayOf(0, 0) + getLocationInWindow(locationInWindow) + val x = locationInWindow[0] + val y = locationInWindow[1] + val boundsInWindow = Rect(x, y, x + width, y + height) + + return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture) +} 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 3209c8bb1f8a..60130e1086ef 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 @@ -18,10 +18,22 @@ package com.android.systemui.testing.screenshot 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 @@ -59,29 +71,39 @@ class ViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { */ fun screenshotTest( goldenIdentifier: String, - layoutParams: LayoutParams = - LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT), - viewProvider: (Activity) -> View, + mode: Mode = Mode.WrapContent, + viewProvider: (ComponentActivity) -> View, ) { activityRule.scenario.onActivity { activity -> // Make sure that the activity draws full screen and fits the whole display instead of // the system bars. - activity.window.setDecorFitsSystemWindows(false) - activity.setContentView(viewProvider(activity), layoutParams) + val window = activity.window + window.setDecorFitsSystemWindows(false) + + // Set the content. + activity.setContentView(viewProvider(activity), mode.layoutParams) + + // Elevation/shadows is not deterministic when doing hardware rendering, so we disable + // it for any view in the hierarchy. + window.decorView.removeElevationRecursively() } // We call onActivity again because it will make sure that our Activity is done measuring, // laying out and drawing its content (that we set in the previous onActivity lambda). + var contentView: View? = null activityRule.scenario.onActivity { activity -> // Check that the content is what we expected. val content = activity.requireViewById<ViewGroup>(android.R.id.content) assertEquals(1, content.childCount) - screenshotRule.assertBitmapAgainstGolden( - content.getChildAt(0).drawIntoBitmap(), - goldenIdentifier, - matcher - ) + contentView = content.getChildAt(0) } + + val bitmap = contentView?.toBitmap() ?: error("contentView is null") + screenshotRule.assertBitmapAgainstGolden( + bitmap, + goldenIdentifier, + matcher, + ) } /** @@ -104,25 +126,78 @@ class ViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { create() window.setWindowAnimations(0) + // Elevation/shadows is not deterministic when doing hardware rendering, so we + // disable it for any view in the hierarchy. + window.decorView.removeElevationRecursively() + // Show the dialog. show() } } - // We call onActivity again because it will make sure that our Dialog is done measuring, - // laying out and drawing its content (that we set in the previous onActivity lambda). - activityRule.scenario.onActivity { - // Check that the content is what we expected. - val dialog = dialog ?: error("dialog is null") - try { - screenshotRule.assertBitmapAgainstGolden( - dialog.window.decorView.drawIntoBitmap(), - goldenIdentifier, - matcher, + try { + val bitmap = dialog?.toBitmap() ?: error("dialog is null") + screenshotRule.assertBitmapAgainstGolden( + bitmap, + goldenIdentifier, + matcher, + ) + } finally { + dialog?.dismiss() + } + } + + 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, ) - } finally { - dialog.dismiss() } } } + + enum class Mode(val layoutParams: LayoutParams) { + WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)), + MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)), + MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)), + MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)), + } } diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt new file mode 100644 index 000000000000..d34f46bf48a6 --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt @@ -0,0 +1,37 @@ +package com.android.systemui.testing.screenshot + +import android.graphics.Bitmap +import android.graphics.Rect +import android.os.Handler +import android.os.Looper +import android.view.PixelCopy +import android.view.Window +import androidx.concurrent.futures.ResolvableFuture + +/* + * This file was forked from androidx/test/core/view/WindowCapture.kt. + * TODO(b/195673633): Remove this fork and use the AndroidX version instead. + */ +fun Window.generateBitmapFromPixelCopy( + boundsInWindow: Rect? = null, + destBitmap: Bitmap, + bitmapFuture: ResolvableFuture<Bitmap> +) { + val onCopyFinished = + PixelCopy.OnPixelCopyFinishedListener { result -> + if (result == PixelCopy.SUCCESS) { + bitmapFuture.set(destBitmap) + } else { + bitmapFuture.setException( + RuntimeException(String.format("PixelCopy failed: %d", result)) + ) + } + } + PixelCopy.request( + this, + boundsInWindow, + destBitmap, + onCopyFinished, + Handler(Looper.getMainLooper()) + ) +} diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 9f790c66302d..114ea657a758 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -50,6 +50,7 @@ android_library { "SystemUIUnfoldLib", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", + "gson-prebuilt-jar", "dagger2", "jsr330", ], diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt new file mode 100644 index 000000000000..916a557d7af8 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt @@ -0,0 +1,65 @@ +/* + * 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.shared.clocks + +import com.android.systemui.plugins.Plugin +import com.android.systemui.plugins.annotations.ProvidesInterface +import android.annotation.FloatRange +import android.graphics.drawable.Drawable +import android.view.View + +/** Identifies a clock design */ +typealias ClockId = String + +/** A Plugin which exposes the ClockProvider interface */ +@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION) +interface ClockProviderPlugin : Plugin, ClockProvider { + companion object { + const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER" + const val VERSION = 1 + } +} + +/** Interface for building clocks and providing information about those clocks */ +interface ClockProvider { + /** Returns metadata for all clocks this provider knows about */ + fun getClocks(): List<ClockMetadata> + + /** Initializes and returns the target clock design */ + fun createClock(id: ClockId): Clock + + /** A static thumbnail for rendering in some examples */ + fun getClockThumbnail(id: ClockId): Drawable? +} + +/** Interface for controlling an active clock */ +interface Clock { + /** A small version of the clock, appropriate for smaller viewports */ + val smallClock: View + + /** A large version of the clock, appropriate when a bigger viewport is available */ + val largeClock: View + + /** Callback to update the clock view to the current time */ + fun onTimeTick() + + /** Sets the level of the AOD transition */ + fun setAodFraction(@FloatRange(from = 0.0, to = 1.0) fraction: Float) +} + +/** Some data about a clock design */ +data class ClockMetadata( + val clockId: ClockId, + val name: String +)
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt new file mode 100644 index 000000000000..32459665e6cf --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -0,0 +1,146 @@ +/* + * 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.shared.clocks + +import android.content.Context +import android.database.ContentObserver +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import android.util.Log +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.PluginListener +import com.android.systemui.shared.plugins.PluginManager +import com.google.gson.Gson +import javax.inject.Inject + +private val TAG = ClockRegistry::class.simpleName +private val DEBUG = true +const val DEFAULT_CLOCK_ID = "DEFAULT" + +typealias ClockChangeListener = () -> Unit + +/** ClockRegistry aggregates providers and plugins */ +open class ClockRegistry @Inject constructor( + val context: Context, + val pluginManager: PluginManager, + @Main val handler: Handler +) { + private val gson = Gson() + private val availableClocks = mutableMapOf<ClockId, ClockInfo>() + private val clockChangeListeners = mutableListOf<ClockChangeListener>() + private val settingObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) = + clockChangeListeners.forEach { it() } + } + + private val pluginListener = object : PluginListener<ClockProviderPlugin> { + override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) { + val currentId = currentClockId + for (clock in plugin.getClocks()) { + val id = clock.clockId + val current = availableClocks[id] + if (current != null) { + Log.e(TAG, "Clock Id conflict: $id is registered by both " + + "${plugin::class.simpleName} and ${current.provider::class.simpleName}") + return + } + + availableClocks[id] = ClockInfo(clock, plugin) + + if (currentId == id) { + if (DEBUG) { + Log.i(TAG, "Current clock ($currentId) was connected") + } + clockChangeListeners.forEach { it() } + } + } + } + + override fun onPluginDisconnected(plugin: ClockProviderPlugin) { + val currentId = currentClockId + for (clock in plugin.getClocks()) { + availableClocks.remove(clock.clockId) + + if (currentId == clock.clockId) { + Log.w(TAG, "Current clock ($currentId) was disconnected") + clockChangeListeners.forEach { it() } + } + } + } + } + + open var currentClockId: ClockId + get() { + val json = Settings.Secure.getString(context.contentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE) + return gson.fromJson(json, ClockSetting::class.java).clockId + } + set(value) { + val json = gson.toJson(ClockSetting(value, System.currentTimeMillis())) + Settings.Secure.putString(context.contentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json) + } + + init { + pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java) + context.contentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), + false, + settingObserver, + UserHandle.USER_ALL) + } + + fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata } + + fun getClockThumbnail(clockId: ClockId): Drawable? = + availableClocks[clockId]?.provider?.getClockThumbnail(clockId) + + fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId) + + fun registerClockChangeListener(listener: ClockChangeListener) = + clockChangeListeners.add(listener) + + fun unregisterClockChangeListener(listener: ClockChangeListener) = + clockChangeListeners.remove(listener) + + fun createCurrentClock(): Clock { + val clockId = currentClockId + if (!clockId.isNullOrEmpty()) { + val clock = createClock(clockId) + if (clock != null) { + return clock + } else { + Log.e(TAG, "Clock $clockId not found; using default") + } + } + + return createClock(DEFAULT_CLOCK_ID)!! + } + + private fun createClock(clockId: ClockId): Clock? = + availableClocks[clockId]?.provider?.createClock(clockId) + + private data class ClockInfo( + val metadata: ClockMetadata, + val provider: ClockProvider + ) + + private data class ClockSetting( + val clockId: ClockId, + val _applied_timestamp: Long + ) +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index 13f1db4a0831..0094820f0dad 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -65,14 +65,6 @@ public class RecentsAnimationControllerCompat { } } - public void hideCurrentInputMethod() { - try { - mAnimationController.hideCurrentInputMethod(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to set hide input method", e); - } - } - /** * Sets the final surface transaction on a Task. This is used by Launcher to notify the system * that animating Activity to PiP has completed and the associated task surface should be diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index ff2a7a132288..609846e8c729 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -369,10 +369,6 @@ public class RemoteTransitionCompat implements Parcelable { if (mWrapped != null) mWrapped.setAnimationTargetsBehindSystemBars(behindSystemBars); } - @Override public void hideCurrentInputMethod() { - mWrapped.hideCurrentInputMethod(); - } - @Override public void setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { mPipTransaction = finishTransaction; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 584beaeea767..c19175742ce5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -41,7 +41,6 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AlarmManager; -import android.app.PendingIntent; import android.app.UserSwitchObserver; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; @@ -150,11 +149,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final boolean DEBUG_SPEW = false; private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600; - private static final String ACTION_FACE_UNLOCK_STARTED - = "com.android.facelock.FACE_UNLOCK_STARTED"; - private static final String ACTION_FACE_UNLOCK_STOPPED - = "com.android.facelock.FACE_UNLOCK_STOPPED"; - // Callback messages private static final int MSG_TIME_UPDATE = 301; private static final int MSG_BATTERY_UPDATE = 302; @@ -170,7 +164,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_FINISHED_GOING_TO_SLEEP = 320; private static final int MSG_STARTED_GOING_TO_SLEEP = 321; private static final int MSG_KEYGUARD_BOUNCER_CHANGED = 322; - private static final int MSG_FACE_UNLOCK_STATE_CHANGED = 327; private static final int MSG_SIM_SUBSCRIPTION_INFO_CHANGED = 328; private static final int MSG_AIRPLANE_MODE_CHANGED = 329; private static final int MSG_SERVICE_STATE_CHANGE = 330; @@ -404,7 +397,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private SparseBooleanArray mUserHasTrust = new SparseBooleanArray(); private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray(); private SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray(); - private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray(); private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>(); @VisibleForTesting @@ -1131,21 +1123,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private void handleFaceUnlockStateChanged(boolean running, int userId) { - Assert.isMainThread(); - mUserFaceUnlockRunning.put(userId, running); - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onFaceUnlockStateChanged(running, userId); - } - } - } - - public boolean isFaceUnlockRunning(int userId) { - return mUserFaceUnlockRunning.get(userId); - } - public boolean isFingerprintDetectionRunning() { return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING; } @@ -1369,16 +1346,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - static class DisplayClientState { - public int clientGeneration; - public boolean clearing; - public PendingIntent intent; - public int playbackState; - public long playbackEventTime; - } - - private DisplayClientState mDisplayClientState = new DisplayClientState(); - @VisibleForTesting protected final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -1452,16 +1419,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final String action = intent.getAction(); if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(action)) { mHandler.sendEmptyMessage(MSG_TIME_UPDATE); - } else if (ACTION_FACE_UNLOCK_STARTED.equals(action)) { - Trace.beginSection( - "KeyguardUpdateMonitor.mBroadcastAllReceiver#onReceive " - + "ACTION_FACE_UNLOCK_STARTED"); - mHandler.sendMessage(mHandler.obtainMessage(MSG_FACE_UNLOCK_STATE_CHANGED, 1, - getSendingUserId())); - Trace.endSection(); - } else if (ACTION_FACE_UNLOCK_STOPPED.equals(action)) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_FACE_UNLOCK_STATE_CHANGED, 0, - getSendingUserId())); } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED .equals(action)) { mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED, @@ -1938,12 +1895,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab handleStartedWakingUp(); Trace.endSection(); break; - case MSG_FACE_UNLOCK_STATE_CHANGED: - Trace.beginSection( - "KeyguardUpdateMonitor#handler MSG_FACE_UNLOCK_STATE_CHANGED"); - handleFaceUnlockStateChanged(msg.arg1 != 0, msg.arg2); - Trace.endSection(); - break; case MSG_SIM_SUBSCRIPTION_INFO_CHANGED: handleSimSubscriptionInfoChanged(); break; @@ -2035,8 +1986,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final IntentFilter allUserFilter = new IntentFilter(); allUserFilter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); - allUserFilter.addAction(ACTION_FACE_UNLOCK_STARTED); - allUserFilter.addAction(ACTION_FACE_UNLOCK_STOPPED); allUserFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); allUserFilter.addAction(ACTION_USER_UNLOCKED); allUserFilter.addAction(ACTION_USER_STOPPED); @@ -3347,7 +3296,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab callback.onTimeChanged(); callback.onPhoneStateChanged(mPhoneState); callback.onRefreshCarrierInfo(); - callback.onClockVisibilityChanged(); callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible); callback.onTelephonyCapable(mTelephonyCapable); @@ -3524,10 +3472,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || state == TelephonyManager.SIM_STATE_PERM_DISABLED); } - public DisplayClientState getCachedDisplayClientState() { - return mDisplayClientState; - } - // TODO: use these callbacks elsewhere in place of the existing notifyScreen*() // (KeyguardViewMediator, KeyguardHostView) public void dispatchStartedWakingUp() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index d420abd688ae..99e0ce29a8c2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -111,12 +111,6 @@ public class KeyguardUpdateMonitorCallback { public void onKeyguardDismissAnimationFinished() { } /** - * Called when visibility of lockscreen clock changes, such as when - * obscured by a widget. - */ - public void onClockVisibilityChanged() { } - - /** * Called when the device becomes provisioned */ public void onDeviceProvisioned() { } @@ -249,11 +243,6 @@ public class KeyguardUpdateMonitorCallback { BiometricSourceType biometricSourceType) { } /** - * Called when the state of face unlock changed. - */ - public void onFaceUnlockStateChanged(boolean running, int userId) { } - - /** * Called when biometric running state changed. */ public void onBiometricRunningStateChanged(boolean running, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java index ca8728aecb4c..8293c74c5e75 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java @@ -23,10 +23,10 @@ import android.view.ViewRootImpl; import androidx.annotation.Nullable; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; /** diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java index 013cdac94ab8..9a0bfc19f848 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java @@ -53,8 +53,11 @@ import javax.inject.Inject; /** * Manages custom clock faces for AOD and lock screen. + * + * @deprecated Migrate to ClockRegistry */ @SysUISingleton +@Deprecated public final class ClockManager { private static final String TAG = "ClockOptsProvider"; diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java index 153da4b6695a..d01c98a934ff 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java @@ -17,9 +17,9 @@ package com.android.keyguard.dagger; import com.android.keyguard.KeyguardStatusViewController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import dagger.BindsInstance; import dagger.Subcomponent; diff --git a/packages/SystemUI/src/com/android/systemui/Somnambulator.java b/packages/SystemUI/src/com/android/systemui/Somnambulator.java index 0dd6d9283223..25801cfe62b0 100644 --- a/packages/SystemUI/src/com/android/systemui/Somnambulator.java +++ b/packages/SystemUI/src/com/android/systemui/Somnambulator.java @@ -17,12 +17,15 @@ package com.android.systemui; import android.app.Activity; -import android.content.Intent; import android.service.dreams.Sandman; /** * A simple activity that launches a dream. * <p> + * + * This activity has been deprecated and no longer used. The system uses its presence to determine + * whether a dock app should be started on dock through intent resolution. + * * Note: This Activity is special. If this class is moved to another package or * renamed, be sure to update the component name in {@link Sandman}. * </p> @@ -34,27 +37,6 @@ public class Somnambulator extends Activity { @Override public void onStart() { super.onStart(); - - final Intent launchIntent = getIntent(); - final String action = launchIntent.getAction(); - if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { - Intent shortcutIntent = new Intent(this, Somnambulator.class); - shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_NEW_TASK); - Intent resultIntent = new Intent(); - resultIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher_dreams)); - resultIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - resultIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.start_dreams)); - setResult(RESULT_OK, resultIntent); - } else { - boolean docked = launchIntent.hasCategory(Intent.CATEGORY_DESK_DOCK); - if (docked) { - Sandman.startDreamWhenDockedIfAppropriate(this); - } else { - Sandman.startDreamByUserRequest(this); - } - } finish(); } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index 5100c09912c8..24fcf15d7a11 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dagger.WMComponent; import com.android.systemui.util.InitializationChecker; import com.android.wm.shell.dagger.WMShellConcurrencyModule; +import com.android.wm.shell.sysui.ShellInterface; import com.android.wm.shell.transition.ShellTransitions; import java.util.Optional; @@ -90,39 +91,33 @@ public abstract class SystemUIInitializer { // Only initialize when not starting from tests since this currently initializes some // components that shouldn't be run in the test environment builder = prepareSysUIComponentBuilder(builder, mWMComponent) + .setShell(mWMComponent.getShell()) .setPip(mWMComponent.getPip()) .setSplitScreen(mWMComponent.getSplitScreen()) .setOneHanded(mWMComponent.getOneHanded()) .setBubbles(mWMComponent.getBubbles()) - .setHideDisplayCutout(mWMComponent.getHideDisplayCutout()) .setShellCommandHandler(mWMComponent.getShellCommandHandler()) .setTaskViewFactory(mWMComponent.getTaskViewFactory()) .setTransitions(mWMComponent.getTransitions()) .setStartingSurface(mWMComponent.getStartingSurface()) .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) - .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) - .setCompatUI(mWMComponent.getCompatUI()) - .setDragAndDrop(mWMComponent.getDragAndDrop()) .setBackAnimation(mWMComponent.getBackAnimation()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. builder = prepareSysUIComponentBuilder(builder, mWMComponent) + .setShell(new ShellInterface() {}) .setPip(Optional.ofNullable(null)) .setSplitScreen(Optional.ofNullable(null)) .setOneHanded(Optional.ofNullable(null)) .setBubbles(Optional.ofNullable(null)) - .setHideDisplayCutout(Optional.ofNullable(null)) .setShellCommandHandler(Optional.ofNullable(null)) .setTaskViewFactory(Optional.ofNullable(null)) .setTransitions(new ShellTransitions() {}) .setDisplayAreaHelper(Optional.ofNullable(null)) .setStartingSurface(Optional.ofNullable(null)) - .setTaskSurfaceHelper(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) - .setCompatUI(Optional.ofNullable(null)) - .setDragAndDrop(Optional.ofNullable(null)) .setBackAnimation(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java index d2703f5e73a2..aff0b1fe287c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java @@ -353,6 +353,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout } mIsShowing = false; + mDragAnimator.cancel(); mWindowManager.removeView(this); setOnApplyWindowInsetsListener(null); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index fb502e5b72cc..cf50f7f8524b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -609,7 +609,7 @@ public class UdfpsController implements DozeReceiver { @NonNull SystemUIDialogManager dialogManager, @NonNull LatencyTracker latencyTracker, @NonNull ActivityLaunchAnimator activityLaunchAnimator, - @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider, + @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider, @BiometricsBackground Executor biometricsExecutor) { mContext = context; mExecution = execution; @@ -639,7 +639,7 @@ public class UdfpsController implements DozeReceiver { mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mLatencyTracker = latencyTracker; mActivityLaunchAnimator = activityLaunchAnimator; - mAlternateTouchProvider = aternateTouchProvider.orElse(null); + mAlternateTouchProvider = alternateTouchProvider.orElse(null); mBiometricExecutor = biometricsExecutor; mOrientationListener = new BiometricDisplayListener( diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index bed553e6e4d6..50ce9d4ec0f4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -20,13 +20,11 @@ import android.app.PendingIntent import android.app.backup.BackupManager import android.content.BroadcastReceiver import android.content.ComponentName -import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.database.ContentObserver import android.net.Uri -import android.os.Environment import android.os.UserHandle import android.service.controls.Control import android.service.controls.actions.ControlAction @@ -43,6 +41,7 @@ import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED @@ -61,6 +60,7 @@ class ControlsControllerImpl @Inject constructor ( private val bindingController: ControlsBindingController, private val listingController: ControlsListingController, private val broadcastDispatcher: BroadcastDispatcher, + private val userFileManager: UserFileManager, optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpManager: DumpManager, userTracker: UserTracker @@ -84,15 +84,12 @@ class ControlsControllerImpl @Inject constructor ( override val currentUserId get() = currentUser.identifier - private val contentResolver: ContentResolver - get() = context.contentResolver - private val persistenceWrapper: ControlsFavoritePersistenceWrapper @VisibleForTesting internal var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper init { - userStructure = UserStructure(context, currentUser) + userStructure = UserStructure(context, currentUser, userFileManager) persistenceWrapper = optionalWrapper.orElseGet { ControlsFavoritePersistenceWrapper( @@ -111,7 +108,7 @@ class ControlsControllerImpl @Inject constructor ( private fun setValuesForUser(newUser: UserHandle) { Log.d(TAG, "Changing to user: $newUser") currentUser = newUser - userStructure = UserStructure(context, currentUser) + userStructure = UserStructure(context, currentUser, userFileManager) persistenceWrapper.changeFileAndBackupManager( userStructure.file, BackupManager(userStructure.userContext) @@ -187,8 +184,11 @@ class ControlsControllerImpl @Inject constructor ( // When a component is uninstalled, allow seeding to happen again if the user // reinstalls the app - val prefs = userStructure.userContext.getSharedPreferences( - PREFS_CONTROLS_FILE, Context.MODE_PRIVATE) + val prefs = userFileManager.getSharedPreferences( + PREFS_CONTROLS_FILE, + Context.MODE_PRIVATE, + userTracker.userId + ) val completedSeedingPackageSet = prefs.getStringSet( PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>()) val servicePackageSet = serviceInfoSet.map { it.packageName } @@ -575,18 +575,12 @@ class ControlsControllerImpl @Inject constructor ( } } -class UserStructure(context: Context, user: UserHandle) { +class UserStructure(context: Context, user: UserHandle, userFileManager: UserFileManager) { val userContext = context.createContextAsUser(user, 0) - - val file = Environment.buildPath( - userContext.filesDir, - ControlsFavoritePersistenceWrapper.FILE_NAME - ) - - val auxiliaryFile = Environment.buildPath( - userContext.filesDir, - AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME - ) + val file = userFileManager.getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, + user.identifier) + val auxiliaryFile = userFileManager.getFile(AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME, + user.identifier) } /** diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 550af7cafc75..fd7680f463c0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -41,16 +41,13 @@ import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; -import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; -import com.android.wm.shell.draganddrop.DragAndDrop; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; -import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; +import com.android.wm.shell.sysui.ShellInterface; import com.android.wm.shell.transition.ShellTransitions; import java.util.Map; @@ -81,6 +78,9 @@ public interface SysUIComponent { @Subcomponent.Builder interface Builder { @BindsInstance + Builder setShell(ShellInterface s); + + @BindsInstance Builder setPip(Optional<Pip> p); @BindsInstance @@ -96,9 +96,6 @@ public interface SysUIComponent { Builder setTaskViewFactory(Optional<TaskViewFactory> t); @BindsInstance - Builder setHideDisplayCutout(Optional<HideDisplayCutout> h); - - @BindsInstance Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump); @BindsInstance @@ -111,18 +108,9 @@ public interface SysUIComponent { Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h); @BindsInstance - Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t); - - @BindsInstance Builder setRecentTasks(Optional<RecentTasks> r); @BindsInstance - Builder setCompatUI(Optional<CompatUI> s); - - @BindsInstance - Builder setDragAndDrop(Optional<DragAndDrop> d); - - @BindsInstance Builder setBackAnimation(Optional<BackAnimation> b); SysUIComponent build(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 664374886df4..fe9622250e67 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -45,6 +45,7 @@ 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; +import com.android.systemui.people.PeopleModule; import com.android.systemui.plugins.BcSmartspaceDataPlugin; import com.android.systemui.privacy.PrivacyModule; import com.android.systemui.recents.Recents; @@ -90,6 +91,7 @@ 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 com.android.wm.shell.sysui.ShellController; import java.util.Optional; import java.util.concurrent.Executor; @@ -123,6 +125,7 @@ import dagger.Provides; LogModule.class, MediaProjectionModule.class, PeopleHubModule.class, + PeopleModule.class, PluginModule.class, PrivacyModule.class, QsFrameTranslateModule.class, @@ -208,7 +211,6 @@ public abstract class SystemUIModule { NotificationShadeWindowController notificationShadeWindowController, KeyguardStateController keyguardStateController, ShadeController shadeController, - ConfigurationController configurationController, @Nullable IStatusBarService statusBarService, INotificationManager notificationManager, NotificationVisibilityProvider visibilityProvider, @@ -226,7 +228,6 @@ public abstract class SystemUIModule { notificationShadeWindowController, keyguardStateController, shadeController, - configurationController, statusBarService, notificationManager, visibilityProvider, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index f2f1798c94f6..e4c0325d4d5a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -29,19 +29,16 @@ import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.dagger.TvWMShellModule; import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; -import com.android.wm.shell.draganddrop.DragAndDrop; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; -import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; +import com.android.wm.shell.sysui.ShellInterface; import com.android.wm.shell.transition.ShellTransitions; import java.util.Optional; @@ -88,6 +85,9 @@ public interface WMComponent { Optional<ShellCommandHandler> getShellCommandHandler(); @WMSingleton + ShellInterface getShell(); + + @WMSingleton Optional<OneHanded> getOneHanded(); @WMSingleton @@ -100,9 +100,6 @@ public interface WMComponent { Optional<Bubbles> getBubbles(); @WMSingleton - Optional<HideDisplayCutout> getHideDisplayCutout(); - - @WMSingleton Optional<TaskViewFactory> getTaskViewFactory(); @WMSingleton @@ -115,17 +112,8 @@ public interface WMComponent { Optional<DisplayAreaHelper> getDisplayAreaHelper(); @WMSingleton - Optional<TaskSurfaceHelper> getTaskSurfaceHelper(); - - @WMSingleton Optional<RecentTasks> getRecentTasks(); @WMSingleton - Optional<CompatUI> getCompatUI(); - - @WMSingleton - Optional<DragAndDrop> getDragAndDrop(); - - @WMSingleton Optional<BackAnimation> getBackAnimation(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index 70e4fa3aa27f..7c816cec08f2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -26,8 +26,6 @@ import android.os.SystemClock; import android.text.format.Formatter; import android.util.Log; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -44,8 +42,6 @@ import javax.inject.Inject; */ @DozeScope public class DozeUi implements DozeMachine.Part { - // if enabled, calls dozeTimeTick() whenever the time changes: - private static final boolean BURN_IN_TESTING_ENABLED = false; private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min private final Context mContext; private final DozeHost mHost; @@ -57,26 +53,13 @@ public class DozeUi implements DozeMachine.Part { private final DozeParameters mDozeParameters; private final DozeLog mDozeLog; private final StatusBarStateController mStatusBarStateController; - private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback = - new KeyguardUpdateMonitorCallback() { - @Override - public void onTimeChanged() { - if (BURN_IN_TESTING_ENABLED && mStatusBarStateController.isDozing()) { - // update whenever the time changes for manual burn in testing - mHost.dozeTimeTick(); - - // Keep wakelock until a frame has been pushed. - mHandler.post(mWakeLock.wrap(() -> {})); - } - } - }; private long mLastTimeTickElapsed = 0; @Inject public DozeUi(Context context, AlarmManager alarmManager, WakeLock wakeLock, DozeHost host, @Main Handler handler, - DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor, + DozeParameters params, StatusBarStateController statusBarStateController, DozeLog dozeLog) { mContext = context; @@ -86,7 +69,6 @@ public class DozeUi implements DozeMachine.Part { mCanAnimateTransition = !params.getDisplayNeedsBlanking(); mDozeParameters = params; mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler); - keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); mDozeLog = dozeLog; mStatusBarStateController = statusBarStateController; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index 59a17bad5069..a25257d6cf42 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -43,6 +43,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { STATUS_ICON_NOTIFICATIONS, STATUS_ICON_WIFI_UNAVAILABLE, STATUS_ICON_ALARM_SET, + STATUS_ICON_CAMERA_DISABLED, + STATUS_ICON_MIC_DISABLED, STATUS_ICON_MIC_CAMERA_DISABLED, STATUS_ICON_PRIORITY_MODE_ON }) @@ -50,8 +52,10 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { public static final int STATUS_ICON_NOTIFICATIONS = 0; public static final int STATUS_ICON_WIFI_UNAVAILABLE = 1; public static final int STATUS_ICON_ALARM_SET = 2; - public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 3; - public static final int STATUS_ICON_PRIORITY_MODE_ON = 4; + public static final int STATUS_ICON_CAMERA_DISABLED = 3; + public static final int STATUS_ICON_MIC_DISABLED = 4; + public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 5; + public static final int STATUS_ICON_PRIORITY_MODE_ON = 6; private final Map<Integer, View> mStatusIcons = new HashMap<>(); @@ -80,6 +84,10 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { fetchStatusIconForResId(R.id.dream_overlay_wifi_status)); mStatusIcons.put(STATUS_ICON_ALARM_SET, fetchStatusIconForResId(R.id.dream_overlay_alarm_set)); + mStatusIcons.put(STATUS_ICON_CAMERA_DISABLED, + fetchStatusIconForResId(R.id.dream_overlay_camera_off)); + mStatusIcons.put(STATUS_ICON_MIC_DISABLED, + fetchStatusIconForResId(R.id.dream_overlay_mic_off)); mStatusIcons.put(STATUS_ICON_MIC_CAMERA_DISABLED, fetchStatusIconForResId(R.id.dream_overlay_camera_mic_off)); mStatusIcons.put(STATUS_ICON_NOTIFICATIONS, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 250313d0aae8..de7bf28c01d6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -214,9 +214,17 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve .isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE); final boolean cameraBlocked = mSensorPrivacyController .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA); - showIcon( - DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, - micBlocked && cameraBlocked); + @DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL; + if (micBlocked && cameraBlocked) { + iconType = DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED; + } else if (!micBlocked && cameraBlocked) { + iconType = DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED; + } else if (micBlocked && !cameraBlocked) { + iconType = DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED; + } + if (iconType != Resources.ID_NULL) { + showIcon(iconType, true); + } } private String buildNotificationsContentDescription(int notificationCount) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/AirQualityColorPicker.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/AirQualityColorPicker.java deleted file mode 100644 index 328753ff1539..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/AirQualityColorPicker.java +++ /dev/null @@ -1,70 +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.dreams.complication; - -import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_DEFAULT; -import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_THRESHOLDS; -import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_VALUES; - -import android.util.Log; - -import androidx.annotation.ColorInt; - -import javax.inject.Inject; -import javax.inject.Named; - -final class AirQualityColorPicker { - private static final String TAG = "AirQualityColorPicker"; - private final int[] mThresholds; - private final int[] mColorValues; - private final int mDefaultColor; - - @Inject - AirQualityColorPicker(@Named(DREAM_AQI_COLOR_THRESHOLDS) int[] thresholds, - @Named(DREAM_AQI_COLOR_VALUES) int[] colorValues, - @Named(DREAM_AQI_COLOR_DEFAULT) @ColorInt int defaultColor) { - mThresholds = thresholds; - mColorValues = colorValues; - mDefaultColor = defaultColor; - } - - @ColorInt - int getColorForValue(String aqiString) { - int size = mThresholds.length; - if (mThresholds.length != mColorValues.length) { - size = Math.min(mThresholds.length, mColorValues.length); - Log.e(TAG, - "Threshold size (" - + mThresholds.length + ") does not match color value size (" - + mColorValues.length - + "). Taking the minimum, some values may be ignored."); - - } - try { - final int value = Integer.parseInt(aqiString.replaceAll("[^0-9]", "")); - for (int i = size - 1; i >= 0; i--) { - if (value > mThresholds[i]) { - return mColorValues[i]; - } - } - Log.e(TAG, "No matching AQI color for value: " + value); - } catch (NumberFormatException e) { - Log.e(TAG, "Could not read AQI value from:" + aqiString); - } - return mDefaultColor; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamAirQualityComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamAirQualityComplication.java deleted file mode 100644 index ba63303d1f2c..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamAirQualityComplication.java +++ /dev/null @@ -1,199 +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.dreams.complication; - -import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COMPLICATION_LAYOUT_PARAMS; -import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COMPLICATION_VIEW; -import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT; - -import android.app.smartspace.SmartspaceAction; -import android.app.smartspace.SmartspaceTarget; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.text.TextUtils; -import android.view.View; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.systemui.CoreStartable; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent; -import com.android.systemui.dreams.smartspace.DreamSmartspaceController; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener; -import com.android.systemui.util.ViewController; - -import javax.inject.Inject; -import javax.inject.Named; - -/** - * Air quality complication which produces view holder responsible for showing AQI over dreams. - */ -public class DreamAirQualityComplication implements Complication { - // TODO(b/236024839): Move to SmartspaceTarget - public static final int FEATURE_AIR_QUALITY = 46; - - private final DreamAirQualityComplicationComponent.Factory mComponentFactory; - - @Inject - public DreamAirQualityComplication( - DreamAirQualityComplicationComponent.Factory componentFactory) { - mComponentFactory = componentFactory; - } - - @Override - public int getRequiredTypeAvailability() { - return COMPLICATION_TYPE_AIR_QUALITY; - } - - @Override - public ViewHolder createView(ComplicationViewModel model) { - return mComponentFactory.create().getViewHolder(); - } - - /** - * {@link CoreStartable} for registering {@link DreamAirQualityComplication} with SystemUI. - */ - public static class Registrant extends CoreStartable { - private final DreamOverlayStateController mDreamOverlayStateController; - private final DreamAirQualityComplication mComplication; - - /** - * Default constructor to register {@link DreamAirQualityComplication}. - */ - @Inject - public Registrant(Context context, - DreamOverlayStateController dreamOverlayStateController, - DreamAirQualityComplication complication) { - super(context); - mDreamOverlayStateController = dreamOverlayStateController; - mComplication = complication; - } - - @Override - public void start() { - // TODO(b/221500478): Only add complication once we have data to show. - mDreamOverlayStateController.addComplication(mComplication); - } - } - - /** - * ViewHolder to contain value/logic associated with the AQI complication view. - */ - public static class DreamAirQualityViewHolder implements ViewHolder { - private final TextView mView; - private final DreamAirQualityViewController mController; - private final ComplicationLayoutParams mLayoutParams; - - @Inject - DreamAirQualityViewHolder(@Named(DREAM_AQI_COMPLICATION_VIEW) TextView view, - DreamAirQualityViewController controller, - @Named(DREAM_AQI_COMPLICATION_LAYOUT_PARAMS) - ComplicationLayoutParams layoutParams) { - mView = view; - mLayoutParams = layoutParams; - mController = controller; - mController.init(); - } - - @Override - public View getView() { - return mView; - } - - @Override - public ComplicationLayoutParams getLayoutParams() { - return mLayoutParams; - } - } - - static class DreamAirQualityViewController extends ViewController<TextView> { - private final DreamSmartspaceController mSmartspaceController; - private final String mSmartspaceTrampolineComponent; - private final ActivityStarter mActivityStarter; - private final AirQualityColorPicker mAirQualityColorPicker; - - private final SmartspaceTargetListener mSmartspaceTargetListener = targets -> { - final SmartspaceTarget target = targets.stream() - .filter(t -> t instanceof SmartspaceTarget) - .map(t -> (SmartspaceTarget) t) - .filter(t -> t.getFeatureType() == FEATURE_AIR_QUALITY) - .findFirst() - .orElse(null); - updateView(target); - }; - - @Inject - DreamAirQualityViewController(@Named(DREAM_AQI_COMPLICATION_VIEW) TextView view, - DreamSmartspaceController smartspaceController, - @Named(SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT) - String smartspaceTrampolineComponent, - ActivityStarter activityStarter, - AirQualityColorPicker airQualityColorPicker) { - super(view); - mSmartspaceController = smartspaceController; - mSmartspaceTrampolineComponent = smartspaceTrampolineComponent; - mActivityStarter = activityStarter; - mAirQualityColorPicker = airQualityColorPicker; - } - - @Override - protected void onViewAttached() { - mSmartspaceController.addUnfilteredListener(mSmartspaceTargetListener); - } - - @Override - protected void onViewDetached() { - mSmartspaceController.removeUnfilteredListener(mSmartspaceTargetListener); - } - - private void updateView(@Nullable SmartspaceTarget target) { - final SmartspaceAction headerAction = target == null ? null : target.getHeaderAction(); - if (headerAction == null || TextUtils.isEmpty(headerAction.getTitle())) { - mView.setVisibility(View.GONE); - return; - } - mView.setVisibility(View.VISIBLE); - - final String airQuality = headerAction.getTitle().toString(); - mView.setText(airQuality); - - final Drawable background = mView.getBackground().mutate(); - final int color = mAirQualityColorPicker.getColorForValue(airQuality); - - if (background instanceof ShapeDrawable) { - ((ShapeDrawable) background).getPaint().setColor(color); - } else if (background instanceof GradientDrawable) { - ((GradientDrawable) background).setColor(color); - } - mView.setBackground(background); - - final Intent intent = headerAction.getIntent(); - if (intent != null && intent.getComponent() != null - && intent.getComponent().getClassName().equals( - mSmartspaceTrampolineComponent)) { - mView.setOnClickListener(v -> { - mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0); - }); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java deleted file mode 100644 index ce61b163dd17..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java +++ /dev/null @@ -1,221 +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.dreams.complication; - -import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS; -import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_VIEW; -import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT; - -import android.app.smartspace.SmartspaceAction; -import android.app.smartspace.SmartspaceTarget; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.text.TextUtils; -import android.widget.TextView; - -import com.android.systemui.CoreStartable; -import com.android.systemui.R; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent; -import com.android.systemui.dreams.smartspace.DreamSmartspaceController; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener; -import com.android.systemui.util.ViewController; - -import javax.inject.Inject; -import javax.inject.Named; - -/** - * Weather Complication that produce Weather view holder. - */ -public class DreamWeatherComplication implements Complication { - DreamWeatherComplicationComponent.Factory mComponentFactory; - - /** - * Default constructor for {@link DreamWeatherComplication}. - */ - @Inject - public DreamWeatherComplication( - DreamWeatherComplicationComponent.Factory componentFactory) { - mComponentFactory = componentFactory; - } - - @Override - public int getRequiredTypeAvailability() { - return COMPLICATION_TYPE_WEATHER; - } - - /** - * Create {@link DreamWeatherViewHolder}. - */ - @Override - public ViewHolder createView(ComplicationViewModel model) { - return mComponentFactory.create().getViewHolder(); - } - - /** - * {@link CoreStartable} for registering {@link DreamWeatherComplication} with SystemUI. - */ - public static class Registrant extends CoreStartable { - private final DreamOverlayStateController mDreamOverlayStateController; - private final DreamWeatherComplication mComplication; - - /** - * Default constructor to register {@link DreamWeatherComplication}. - */ - @Inject - public Registrant(Context context, - DreamOverlayStateController dreamOverlayStateController, - DreamWeatherComplication dreamWeatherComplication) { - super(context); - mDreamOverlayStateController = dreamOverlayStateController; - mComplication = dreamWeatherComplication; - } - - @Override - public void start() { - mDreamOverlayStateController.addComplication(mComplication); - } - } - - /** - * ViewHolder to contain value/logic associated with a Weather Complication View. - */ - public static class DreamWeatherViewHolder implements ViewHolder { - private final TextView mView; - private final ComplicationLayoutParams mLayoutParams; - private final DreamWeatherViewController mViewController; - - @Inject - DreamWeatherViewHolder( - @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view, - DreamWeatherViewController controller, - @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS) - ComplicationLayoutParams layoutParams) { - mView = view; - mLayoutParams = layoutParams; - mViewController = controller; - mViewController.init(); - } - - @Override - public TextView getView() { - return mView; - } - - @Override - public ComplicationLayoutParams getLayoutParams() { - return mLayoutParams; - } - } - - /** - * ViewController to contain value/logic associated with a Weather Complication View. - */ - static class DreamWeatherViewController extends ViewController<TextView> { - private final DreamSmartspaceController mSmartSpaceController; - private final ActivityStarter mActivityStarter; - private final String mSmartspaceTrampolineActivityComponent; - private SmartspaceTargetListener mSmartspaceTargetListener; - private final Resources mResources; - - @Inject - DreamWeatherViewController( - @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view, - @Named(SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT) String smartspaceTrampoline, - ActivityStarter activityStarter, - DreamSmartspaceController smartspaceController, - Resources resources - ) { - super(view); - mActivityStarter = activityStarter; - mResources = resources; - mSmartSpaceController = smartspaceController; - mSmartspaceTrampolineActivityComponent = smartspaceTrampoline; - } - - @Override - protected void onViewAttached() { - mSmartspaceTargetListener = targets -> targets.forEach( - t -> { - if (t instanceof SmartspaceTarget - && ((SmartspaceTarget) t).getFeatureType() - == SmartspaceTarget.FEATURE_WEATHER) { - final SmartspaceTarget target = (SmartspaceTarget) t; - final SmartspaceAction headerAction = target.getHeaderAction(); - if (headerAction == null || TextUtils.isEmpty( - headerAction.getTitle())) { - return; - } - - final CharSequence temperature = headerAction.getTitle(); - mView.setText(temperature); - mView.setContentDescription(getFormattedContentDescription(temperature, - headerAction.getContentDescription())); - final Icon icon = headerAction.getIcon(); - if (icon != null) { - final int iconSize = - getResources().getDimensionPixelSize( - R.dimen.smart_action_button_icon_size); - final Drawable iconDrawable = icon.loadDrawable(getContext()); - iconDrawable.setBounds(0, 0, iconSize, iconSize); - mView.setCompoundDrawables(iconDrawable, null, null, null); - mView.setCompoundDrawablePadding( - getResources().getDimensionPixelSize( - R.dimen.smart_action_button_icon_padding)); - } - mView.setOnClickListener(v -> { - final Intent intent = headerAction.getIntent(); - if (intent != null && intent.getComponent() != null - && intent.getComponent().getClassName() - .equals(mSmartspaceTrampolineActivityComponent)) { - mActivityStarter.postStartActivityDismissingKeyguard( - intent, 0 /*delay*/); - } - }); - } - }); - // We need to use an unfiltered listener here since weather is filtered from showing - // in the dream smartspace. - mSmartSpaceController.addUnfilteredListener(mSmartspaceTargetListener); - } - - @Override - protected void onViewDetached() { - mSmartSpaceController.removeUnfilteredListener(mSmartspaceTargetListener); - } - - /** - * Returns a formatted content description for accessibility of the weather condition and - * temperature. - */ - private CharSequence getFormattedContentDescription(CharSequence temperature, - CharSequence weatherCondition) { - if (TextUtils.isEmpty(temperature)) { - return weatherCondition; - } else if (TextUtils.isEmpty(weatherCondition)) { - return temperature; - } - - return mResources.getString(R.string.dream_overlay_weather_complication_desc, - weatherCondition, temperature); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamAirQualityComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamAirQualityComplicationComponent.java deleted file mode 100644 index 112a1cea98a2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamAirQualityComplicationComponent.java +++ /dev/null @@ -1,137 +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.dreams.complication.dagger; - -import android.content.Context; -import android.content.res.Resources; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.ColorInt; - -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dreams.complication.ComplicationLayoutParams; -import com.android.systemui.dreams.complication.DreamAirQualityComplication; -import com.android.systemui.dreams.complication.DreamAirQualityComplication.DreamAirQualityViewHolder; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -import javax.inject.Named; -import javax.inject.Scope; - -import dagger.Module; -import dagger.Provides; -import dagger.Subcomponent; - -/** - * Component responsible for generating dependencies for the {@link DreamAirQualityComplication}, - * such as the layout details. - */ -@Subcomponent(modules = { - DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.class, -}) -@DreamAirQualityComplicationComponent.DreamAirQualityComplicationScope -public interface DreamAirQualityComplicationComponent { - - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Scope - @interface DreamAirQualityComplicationScope { - } - - /** - * Generates {@link DreamAirQualityComplicationComponent}. - */ - @Subcomponent.Factory - interface Factory { - DreamAirQualityComplicationComponent create(); - } - - /** - * Creates {@link DreamAirQualityViewHolder}. - */ - DreamAirQualityViewHolder getViewHolder(); - - /** - * Scoped values for {@link DreamAirQualityComplicationComponent}. - */ - @Module - interface DreamAirQualityComplicationModule { - String DREAM_AQI_COMPLICATION_VIEW = "aqi_complication_view"; - String DREAM_AQI_COMPLICATION_LAYOUT_PARAMS = "aqi_complication_layout_params"; - String DREAM_AQI_COLOR_THRESHOLDS = "aqi_color_thresholds"; - String DREAM_AQI_COLOR_VALUES = "aqi_color_values"; - String DREAM_AQI_COLOR_DEFAULT = "aqi_color_default"; - // Order weight of insert into parent container - int INSERT_ORDER_WEIGHT = 1; - - /** - * Provides the complication view. - */ - @Provides - @DreamAirQualityComplicationScope - @Named(DREAM_AQI_COMPLICATION_VIEW) - static TextView provideComplicationView(LayoutInflater layoutInflater) { - return Objects.requireNonNull((TextView) - layoutInflater.inflate(R.layout.dream_overlay_complication_aqi, - null, false), - "R.layout.dream_overlay_complication_aqi did not properly inflated"); - } - - /** - * Provides the layout parameters for the complication view. - */ - @Provides - @DreamAirQualityComplicationScope - @Named(DREAM_AQI_COMPLICATION_LAYOUT_PARAMS) - static ComplicationLayoutParams provideLayoutParams() { - return new ComplicationLayoutParams(0, - ViewGroup.LayoutParams.WRAP_CONTENT, - ComplicationLayoutParams.POSITION_BOTTOM - | ComplicationLayoutParams.POSITION_START, - ComplicationLayoutParams.DIRECTION_END, - INSERT_ORDER_WEIGHT, /* snapToGuide= */ true); - } - - @Provides - @DreamAirQualityComplicationScope - @Named(DREAM_AQI_COLOR_THRESHOLDS) - static int[] provideAqiColorThresholds(@Main Resources resources) { - return resources.getIntArray(R.array.config_dreamAqiThresholds); - } - - @Provides - @DreamAirQualityComplicationScope - @Named(DREAM_AQI_COLOR_VALUES) - static int[] provideAqiColorValues(@Main Resources resources) { - return resources.getIntArray(R.array.config_dreamAqiColorValues); - } - - @Provides - @DreamAirQualityComplicationScope - @Named(DREAM_AQI_COLOR_DEFAULT) - @ColorInt - static int provideDefaultAqiColor(Context context) { - return context.getColor(R.color.dream_overlay_aqi_unknown); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java deleted file mode 100644 index f1a16897fdbc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java +++ /dev/null @@ -1,120 +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.dreams.complication.dagger; - - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import android.content.res.Resources; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.android.internal.util.Preconditions; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dreams.complication.ComplicationLayoutParams; -import com.android.systemui.dreams.complication.DreamWeatherComplication.DreamWeatherViewHolder; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import javax.inject.Named; -import javax.inject.Scope; - -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.Subcomponent; - -/** - * {@link DreamWeatherComplicationComponent} is responsible for generating dependencies surrounding - * the - * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout - * details. - */ -@Subcomponent(modules = { - DreamWeatherComplicationComponent.DreamWeatherComplicationModule.class, -}) -@DreamWeatherComplicationComponent.DreamWeatherComplicationScope -public interface DreamWeatherComplicationComponent { - /** - * Creates {@link DreamWeatherViewHolder}. - */ - DreamWeatherViewHolder getViewHolder(); - - @Documented - @Retention(RUNTIME) - @Scope - @interface DreamWeatherComplicationScope { - } - - /** - * Generates {@link DreamWeatherComplicationComponent}. - */ - @Subcomponent.Factory - interface Factory { - DreamWeatherComplicationComponent create(); - } - - /** - * Scoped values for {@link DreamWeatherComplicationComponent}. - */ - @Module - interface DreamWeatherComplicationModule { - String DREAM_WEATHER_COMPLICATION_VIEW = "weather_complication_view"; - String DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS = - "weather_complication_layout_params"; - // Order weight of insert into parent container - int INSERT_ORDER_WEIGHT = 2; - - /** - * Provides the complication view. - */ - @Provides - @DreamWeatherComplicationScope - @Named(DREAM_WEATHER_COMPLICATION_VIEW) - static TextView provideComplicationView(LayoutInflater layoutInflater) { - return Preconditions.checkNotNull((TextView) - layoutInflater.inflate(R.layout.dream_overlay_complication_weather, - null, false), - "R.layout.dream_overlay_complication_weather did not properly inflated"); - } - - /** - * Provides the layout parameters for the complication view. - */ - @Provides - @DreamWeatherComplicationScope - @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS) - static ComplicationLayoutParams provideLayoutParams() { - return new ComplicationLayoutParams(0, - ViewGroup.LayoutParams.WRAP_CONTENT, - ComplicationLayoutParams.POSITION_BOTTOM - | ComplicationLayoutParams.POSITION_START, - ComplicationLayoutParams.DIRECTION_END, - INSERT_ORDER_WEIGHT, /* snapToGuide= */ true); - } - - /** - * Binds resources in the dream weather complication scope. - */ - @Binds - @DreamWeatherComplicationScope - Resources getResources(@Main Resources resources); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java index 98344aa7aae6..e45437dedd6e 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java @@ -16,15 +16,9 @@ package com.android.systemui.dreams.complication.dagger; -import android.content.Context; - -import com.android.systemui.R; import com.android.systemui.dagger.SystemUIBinder; -import javax.inject.Named; - import dagger.Module; -import dagger.Provides; /** * Module for all components with corresponding dream layer complications registered in @@ -33,20 +27,6 @@ import dagger.Provides; @Module(includes = { DreamClockDateComplicationModule.class, DreamClockTimeComplicationModule.class, - }, - subcomponents = { - DreamWeatherComplicationComponent.class, - DreamAirQualityComplicationComponent.class, }) public interface RegisteredComplicationsModule { - String SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT = "smartspace_trampoline_activity"; - - /** - * Provides the smartspace trampoline activity component. - */ - @Provides - @Named(SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT) - static String provideSmartspaceTrampolineActivityComponent(Context context) { - return context.getString(R.string.config_smartspaceTrampolineActivityComponent); - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5b90ed834253..e913d2b86306 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -122,6 +122,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -131,7 +132,6 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -556,11 +556,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } @Override - public void onClockVisibilityChanged() { - adjustStatusBarLocked(); - } - - @Override public void onDeviceProvisioned() { sendUserPresentBroadcast(); synchronized (KeyguardViewMediator.this) { @@ -1193,6 +1188,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mSystemReady = true; doKeyguardLocked(null); mUpdateMonitor.registerCallback(mUpdateCallback); + adjustStatusBarLocked(); mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback); } // Most services aren't available until the system reaches the ready state, so we diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index aeff2d41bf92..012d76651b23 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -682,9 +682,22 @@ public class MediaControlPanel { Drawable artwork; boolean isArtworkBound; Icon artworkIcon = data.getArtwork(); + WallpaperColors wallpaperColors = null; if (artworkIcon != null) { - WallpaperColors wallpaperColors = WallpaperColors - .fromBitmap(artworkIcon.getBitmap()); + if (artworkIcon.getType() == Icon.TYPE_BITMAP + || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) { + // Avoids extra processing if this is already a valid bitmap + wallpaperColors = WallpaperColors + .fromBitmap(artworkIcon.getBitmap()); + } else { + Drawable artworkDrawable = artworkIcon.loadDrawable(mContext); + if (artworkDrawable != null) { + wallpaperColors = WallpaperColors + .fromDrawable(artworkIcon.loadDrawable(mContext)); + } + } + } + if (wallpaperColors != null) { mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT); Drawable albumArt = getScaledBackground(artworkIcon, width, height); GradientDrawable gradient = (GradientDrawable) mContext diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index ec4c4e6b3363..e9b6af44ddf3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -60,7 +60,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @Override public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) { final int size = mController.getMediaDevices().size(); - if (position == size && mController.isZeroMode()) { + if (position == size) { viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */, true /* bottomMargin */); } else if (position < size) { @@ -75,7 +75,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @Override public long getItemId(int position) { final int size = mController.getMediaDevices().size(); - if (position == size && mController.isZeroMode()) { + if (position == size) { return -1; } else if (position < size) { return ((List<MediaDevice>) (mController.getMediaDevices())) @@ -88,11 +88,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @Override public int getItemCount() { - if (mController.isZeroMode()) { - // Add extra one for "pair new" or dynamic group - return mController.getMediaDevices().size() + 1; - } - return mController.getMediaDevices().size(); + // Add extra one for "pair new" + return mController.getMediaDevices().size() + 1; } class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 247ffa7c2ef9..7e3275da8c31 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -16,7 +16,7 @@ package com.android.systemui.media.dialog; -import static android.provider.Settings.ACTION_BLUETOOTH_PAIRING_SETTINGS; +import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS; import android.annotation.CallbackExecutor; import android.app.AlertDialog; @@ -300,6 +300,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return; } try { + synchronized (mMediaDevicesLock) { + mMediaDevices.removeIf(MediaDevice::isMutingExpectedDevice); + } mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice()); } catch (Exception e) { Log.d(TAG, "Unable to cancel mute await connection"); @@ -711,22 +714,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return false; } - boolean isZeroMode() { - synchronized (mMediaDevicesLock) { - if (mMediaDevices.size() == 1) { - final MediaDevice device = mMediaDevices.iterator().next(); - // Add "pair new" only when local output device exists - final int type = device.getDeviceType(); - if (type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE - || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE - || type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) { - return true; - } - } - return false; - } - } - void launchBluetoothPairing(View view) { ActivityLaunchAnimator.Controller controller = mDialogLaunchAnimator.createActivityLaunchController(view); @@ -736,7 +723,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } Intent launchIntent = - new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS) + new Intent(ACTION_BLUETOOTH_SETTINGS) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); final Intent deepLinkIntent = new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index b01dca1a0365..9e1ba5f723a3 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -72,6 +72,7 @@ import com.android.systemui.navigationbar.buttons.NearestTouchFrame; import com.android.systemui.navigationbar.buttons.RotationContextButton; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.recents.Recents; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shared.rotation.FloatingRotationButton; import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.rotation.RotationButtonController; @@ -81,7 +82,6 @@ import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarTransitionsController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt new file mode 100644 index 000000000000..dd35445f9b7b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt @@ -0,0 +1,32 @@ +/* + * 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.people + +import com.android.systemui.people.data.repository.PeopleTileRepository +import com.android.systemui.people.data.repository.PeopleTileRepositoryImpl +import com.android.systemui.people.data.repository.PeopleWidgetRepository +import com.android.systemui.people.data.repository.PeopleWidgetRepositoryImpl +import dagger.Binds +import dagger.Module + +/** Dagger module to provide/bind people space dependencies. */ +@Module +interface PeopleModule { + @Binds fun bindTileRepository(impl: PeopleTileRepositoryImpl): PeopleTileRepository + + @Binds fun bindWidgetRepository(impl: PeopleWidgetRepositoryImpl): PeopleWidgetRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 93a3f81fdd6b..e845aa85a121 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -19,144 +19,52 @@ package com.android.systemui.people; import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; -import static com.android.systemui.people.PeopleTileViewHelper.getPersonIconBitmap; -import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp; - -import android.app.Activity; -import android.app.people.PeopleSpaceTile; -import android.content.Context; import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Outline; -import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.widget.LinearLayout; -import com.android.systemui.R; -import com.android.systemui.people.widget.PeopleSpaceWidgetManager; -import com.android.systemui.people.widget.PeopleTileKey; +import androidx.activity.ComponentActivity; +import androidx.lifecycle.ViewModelProvider; -import java.util.ArrayList; -import java.util.List; +import com.android.systemui.people.ui.view.PeopleViewBinder; +import com.android.systemui.people.ui.viewmodel.PeopleViewModel; import javax.inject.Inject; /** People Tile Widget configuration activity that shows the user their conversation tiles. */ -public class PeopleSpaceActivity extends Activity { +public class PeopleSpaceActivity extends ComponentActivity { private static final String TAG = "PeopleSpaceActivity"; private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; - private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; - private Context mContext; - private int mAppWidgetId; + private final PeopleViewModel.Factory mViewModelFactory; + private PeopleViewModel mViewModel; @Inject - public PeopleSpaceActivity(PeopleSpaceWidgetManager peopleSpaceWidgetManager) { + public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) { super(); - mPeopleSpaceWidgetManager = peopleSpaceWidgetManager; - + mViewModelFactory = viewModelFactory; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mContext = getApplicationContext(); - mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, - INVALID_APPWIDGET_ID); setResult(RESULT_CANCELED); - } - - /** Builds the conversation selection activity. */ - private void buildActivity() { - List<PeopleSpaceTile> priorityTiles = new ArrayList<>(); - List<PeopleSpaceTile> recentTiles = new ArrayList<>(); - try { - priorityTiles = mPeopleSpaceWidgetManager.getPriorityTiles(); - recentTiles = mPeopleSpaceWidgetManager.getRecentTiles(); - } catch (Exception e) { - Log.e(TAG, "Couldn't retrieve conversations", e); - } - - // If no conversations, render activity without conversations - if (recentTiles.isEmpty() && priorityTiles.isEmpty()) { - setContentView(R.layout.people_space_activity_no_conversations); - - // The Tile preview has colorBackground as its background. Change it so it's different - // than the activity's background. - LinearLayout item = findViewById(android.R.id.background); - GradientDrawable shape = (GradientDrawable) item.getBackground(); - final TypedArray ta = mContext.getTheme().obtainStyledAttributes( - new int[]{com.android.internal.R.attr.colorSurface}); - shape.setColor(ta.getColor(0, Color.WHITE)); - return; - } - - setContentView(R.layout.people_space_activity); - setTileViews(R.id.priority, R.id.priority_tiles, priorityTiles); - setTileViews(R.id.recent, R.id.recent_tiles, recentTiles); - } - - private ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), - mContext.getResources().getDimension(R.dimen.people_space_widget_radius)); - } - }; - - /** Sets a {@link PeopleSpaceTileView}s for each conversation. */ - private void setTileViews(int viewId, int tilesId, List<PeopleSpaceTile> tiles) { - if (tiles.isEmpty()) { - LinearLayout view = findViewById(viewId); - view.setVisibility(View.GONE); - return; - } - - ViewGroup layout = findViewById(tilesId); - layout.setClipToOutline(true); - layout.setOutlineProvider(mViewOutlineProvider); - for (int i = 0; i < tiles.size(); ++i) { - PeopleSpaceTile tile = tiles.get(i); - PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext, - layout, tile.getId(), i == (tiles.size() - 1)); - setTileView(tileView, tile); - } - } - - /** Sets {@code tileView} with the data in {@code conversation}. */ - private void setTileView(PeopleSpaceTileView tileView, PeopleSpaceTile tile) { - try { - if (tile.getUserName() != null) { - tileView.setName(tile.getUserName().toString()); - } - tileView.setPersonIcon(getPersonIconBitmap(mContext, tile, - getSizeInDp(mContext, R.dimen.avatar_size_for_medium, - mContext.getResources().getDisplayMetrics().density))); - - PeopleTileKey key = new PeopleTileKey(tile); - tileView.setOnClickListener(v -> storeWidgetConfiguration(tile, key)); - } catch (Exception e) { - Log.e(TAG, "Couldn't retrieve shortcut information", e); - } - } - - /** Stores the user selected configuration for {@code mAppWidgetId}. */ - private void storeWidgetConfiguration(PeopleSpaceTile tile, PeopleTileKey key) { - if (PeopleSpaceUtils.DEBUG) { - if (DEBUG) { - Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: " - + tile.getId() + " for widget ID: " - + mAppWidgetId); - } - } - mPeopleSpaceWidgetManager.addNewWidget(mAppWidgetId, key); - finishActivity(); + mViewModel = new ViewModelProvider(this, mViewModelFactory).get(PeopleViewModel.class); + + // Update the widget ID coming from the intent. + int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID); + mViewModel.onWidgetIdChanged(widgetId); + + ViewGroup view = PeopleViewBinder.create(this); + PeopleViewBinder.bind(view, mViewModel, /* lifecycleOwner= */ this, + () -> { + finishActivity(); + return null; + }); + setContentView(view); } /** Finish activity with a successful widget configuration result. */ @@ -169,19 +77,13 @@ public class PeopleSpaceActivity extends Activity { /** Finish activity without choosing a widget. */ public void dismissActivity(View v) { if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!"); + setResult(RESULT_CANCELED); finish(); } private void setActivityResult(int result) { Intent resultValue = new Intent(); - resultValue.putExtra(EXTRA_APPWIDGET_ID, mAppWidgetId); + resultValue.putExtra(EXTRA_APPWIDGET_ID, mViewModel.getAppWidgetId().getValue()); setResult(result, resultValue); } - - @Override - protected void onResume() { - super.onResume(); - // Refresh tile views to sync new conversations. - buildActivity(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java index 4ee951f3cdb1..58e700f81388 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java @@ -28,6 +28,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.IconDrawableFactory; import android.util.Log; +import android.view.ContextThemeWrapper; import androidx.core.graphics.drawable.RoundedBitmapDrawable; @@ -52,16 +53,15 @@ class PeopleStoryIconFactory implements AutoCloseable { PeopleStoryIconFactory(Context context, PackageManager pm, IconDrawableFactory iconDrawableFactory, int iconSizeDp) { - context.setTheme(android.R.style.Theme_DeviceDefault_DayNight); - mIconBitmapSize = (int) (iconSizeDp * context.getResources().getDisplayMetrics().density); - mDensity = context.getResources().getDisplayMetrics().density; + mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_DayNight); + mIconBitmapSize = (int) (iconSizeDp * mContext.getResources().getDisplayMetrics().density); + mDensity = mContext.getResources().getDisplayMetrics().density; mIconSize = mDensity * iconSizeDp; mPackageManager = pm; mIconDrawableFactory = iconDrawableFactory; - mImportantConversationColor = context.getColor(R.color.important_conversation); - mAccentColor = Utils.getColorAttr(context, + mImportantConversationColor = mContext.getColor(R.color.important_conversation); + mAccentColor = Utils.getColorAttr(mContext, com.android.internal.R.attr.colorAccentPrimaryVariant).getDefaultColor(); - mContext = context; } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 00aa1381ace1..be82b1faac8e 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -75,6 +75,7 @@ import androidx.core.math.MathUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import com.android.systemui.people.data.model.PeopleTileModel; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import com.android.systemui.people.widget.PeopleTileKey; @@ -299,7 +300,8 @@ public class PeopleTileViewHelper { return createLastInteractionRemoteViews(); } - private static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) { + /** Whether the conversation associated with {@code tile} can bypass DND. */ + public static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) { if (tile == null) return false; int notificationPolicyState = tile.getNotificationPolicyState(); @@ -536,7 +538,8 @@ public class PeopleTileViewHelper { return views; } - private static boolean getHasNewStory(PeopleSpaceTile tile) { + /** Whether {@code tile} has a new story. */ + public static boolean getHasNewStory(PeopleSpaceTile tile) { return tile.getStatuses() != null && tile.getStatuses().stream().anyMatch( c -> c.getActivity() == ACTIVITY_NEW_STORY); } @@ -1250,16 +1253,24 @@ public class PeopleTileViewHelper { } /** Returns a bitmap with the user icon and package icon. */ - public static Bitmap getPersonIconBitmap(Context context, PeopleSpaceTile tile, + public static Bitmap getPersonIconBitmap(Context context, PeopleTileModel tile, int maxAvatarSize) { - boolean hasNewStory = getHasNewStory(tile); - return getPersonIconBitmap(context, tile, maxAvatarSize, hasNewStory); + return getPersonIconBitmap(context, maxAvatarSize, tile.getHasNewStory(), + tile.getUserIcon(), tile.getKey().getPackageName(), tile.getKey().getUserId(), + tile.isImportant(), tile.isDndBlocking()); } /** Returns a bitmap with the user icon and package icon. */ private static Bitmap getPersonIconBitmap( Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) { - Icon icon = tile.getUserIcon(); + return getPersonIconBitmap(context, maxAvatarSize, hasNewStory, tile.getUserIcon(), + tile.getPackageName(), getUserId(tile), + tile.isImportantConversation(), isDndBlockingTileData(tile)); + } + + private static Bitmap getPersonIconBitmap( + Context context, int maxAvatarSize, boolean hasNewStory, Icon icon, String packageName, + int userId, boolean importantConversation, boolean dndBlockingTileData) { if (icon == null) { Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate(); placeholder.setColorFilter(getDisabledColorFilter()); @@ -1272,10 +1283,10 @@ public class PeopleTileViewHelper { RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create( context.getResources(), icon.getBitmap()); Drawable personDrawable = storyIcon.getPeopleTileDrawable(roundedDrawable, - tile.getPackageName(), getUserId(tile), tile.isImportantConversation(), + packageName, userId, importantConversation, hasNewStory); - if (isDndBlockingTileData(tile)) { + if (dndBlockingTileData) { personDrawable.setColorFilter(getDisabledColorFilter()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt index edeff6e37182..5d8539fabc6b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java +++ b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,21 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.draganddrop; +package com.android.systemui.people.data.model -import android.content.res.Configuration; +import android.graphics.drawable.Icon +import com.android.systemui.people.widget.PeopleTileKey -import com.android.wm.shell.common.annotations.ExternalThread; - -/** - * Interface for telling DragAndDrop stuff. - */ -@ExternalThread -public interface DragAndDrop { - - /** Called when the theme changes. */ - void onThemeChanged(); - - /** Called when the configuration changes. */ - void onConfigChanged(Configuration newConfig); -} +/** Models a tile/conversation. */ +data class PeopleTileModel( + val key: PeopleTileKey, + val username: String, + val userIcon: Icon, + val hasNewStory: Boolean, + val isImportant: Boolean, + val isDndBlocking: Boolean, +) diff --git a/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt new file mode 100644 index 000000000000..01b43d52130b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt @@ -0,0 +1,61 @@ +/* + * 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.people.data.repository + +import android.app.people.PeopleSpaceTile +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.people.PeopleTileViewHelper +import com.android.systemui.people.data.model.PeopleTileModel +import com.android.systemui.people.widget.PeopleSpaceWidgetManager +import com.android.systemui.people.widget.PeopleTileKey +import javax.inject.Inject + +/** A Repository to fetch the current tiles/conversations. */ +// TODO(b/238993727): Make the tiles API reactive. +interface PeopleTileRepository { + /* The current priority tiles. */ + fun priorityTiles(): List<PeopleTileModel> + + /* The current recent tiles. */ + fun recentTiles(): List<PeopleTileModel> +} + +@SysUISingleton +class PeopleTileRepositoryImpl +@Inject +constructor( + private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager, +) : PeopleTileRepository { + override fun priorityTiles(): List<PeopleTileModel> { + return peopleSpaceWidgetManager.priorityTiles.map { it.toModel() } + } + + override fun recentTiles(): List<PeopleTileModel> { + return peopleSpaceWidgetManager.recentTiles.map { it.toModel() } + } + + private fun PeopleSpaceTile.toModel(): PeopleTileModel { + return PeopleTileModel( + PeopleTileKey(this), + userName.toString(), + userIcon, + PeopleTileViewHelper.getHasNewStory(this), + isImportantConversation, + PeopleTileViewHelper.isDndBlockingTileData(this), + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt new file mode 100644 index 000000000000..f2b6cb1fc3f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt @@ -0,0 +1,43 @@ +/* + * 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.people.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.people.widget.PeopleSpaceWidgetManager +import com.android.systemui.people.widget.PeopleTileKey +import javax.inject.Inject + +interface PeopleWidgetRepository { + /** + * Bind the widget with ID [widgetId] to the tile keyed by [tileKey]. + * + * If there is already a widget with [widgetId], this existing widget will be reconfigured and + * associated to this tile. If there is no widget with [widgetId], a new one will be created. + */ + fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) +} + +@SysUISingleton +class PeopleWidgetRepositoryImpl +@Inject +constructor( + private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager, +) : PeopleWidgetRepository { + override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) { + peopleSpaceWidgetManager.addNewWidget(widgetId, tileKey) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt new file mode 100644 index 000000000000..bc982cccaacd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt @@ -0,0 +1,243 @@ +/* + * 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.people.ui.view + +import android.content.Context +import android.graphics.Color +import android.graphics.Outline +import android.graphics.drawable.GradientDrawable +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewOutlineProvider +import android.widget.LinearLayout +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.Lifecycle.State.CREATED +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.people.PeopleSpaceTileView +import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel +import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +/** A ViewBinder for [PeopleViewModel]. */ +object PeopleViewBinder { + private const val TAG = "PeopleSpaceViewBinder" + + /** + * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists. + */ + private val ViewOutlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + 0, + view.width, + view.height, + view.context.resources.getDimension(R.dimen.people_space_widget_radius), + ) + } + } + + /** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */ + @JvmStatic + fun create(context: Context): ViewGroup { + return LayoutInflater.from(context) + .inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup + } + + /** Bind [view] to [viewModel]. */ + @JvmStatic + fun bind( + view: ViewGroup, + viewModel: PeopleViewModel, + lifecycleOwner: LifecycleOwner, + onFinish: () -> Unit, + ) { + // Call [onFinish] this activity when the ViewModel tells us so. + lifecycleOwner.lifecycleScope.launch { + lifecycleOwner.repeatOnLifecycle(CREATED) { + viewModel.isFinished.collect { isFinished -> + if (isFinished) { + viewModel.clearIsFinished() + onFinish() + } + } + } + } + + // Start collecting the UI data once the Activity is STARTED. + lifecycleOwner.lifecycleScope.launch { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + combine( + viewModel.priorityTiles, + viewModel.recentTiles, + ) { priority, recent -> + priority to recent + } + .collect { (priorityTiles, recentTiles) -> + if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) { + setConversationsContent( + view, + priorityTiles, + recentTiles, + viewModel::onTileClicked, + ) + } else { + setNoConversationsContent(view) + } + } + } + } + + // Make sure to refresh the tiles/conversations when the Activity is resumed, so that it + // updates them when going back to the Activity after leaving it. + lifecycleOwner.lifecycleScope.launch { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.onTileRefreshRequested() + } + } + } + + private fun setNoConversationsContent(view: ViewGroup) { + // This should never happen. + if (view.childCount > 1) { + error("view has ${view.childCount} children, it should have maximum 1") + } + + // The static content for no conversations is already shown. + if (view.findViewById<View>(R.id.top_level_no_conversations) != null) { + return + } + + // If we were showing the content with conversations earlier, remove it. + if (view.childCount == 1) { + view.removeViewAt(0) + } + + val context = view.context + val noConversationsView = + LayoutInflater.from(context) + .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view) + + // The Tile preview has colorBackground as its background. Change it so it's different than + // the activity's background. + val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background) + val shape = item.background as GradientDrawable + val ta = + context.theme.obtainStyledAttributes( + intArrayOf(com.android.internal.R.attr.colorSurface) + ) + shape.setColor(ta.getColor(0, Color.WHITE)) + ta.recycle() + } + + private fun setConversationsContent( + view: ViewGroup, + priorityTiles: List<PeopleTileViewModel>, + recentTiles: List<PeopleTileViewModel>, + onTileClicked: (PeopleTileViewModel) -> Unit, + ) { + // This should never happen. + if (view.childCount > 1) { + error("view has ${view.childCount} children, it should have maximum 1") + } + + // Inflate the content with conversations, if it's not already. + if (view.findViewById<View>(R.id.top_level_with_conversations) == null) { + // If we were showing the content without conversations earlier, remove it. + if (view.childCount == 1) { + view.removeViewAt(0) + } + + LayoutInflater.from(view.context) + .inflate(R.layout.people_space_activity_with_conversations, /* root= */ view) + } + + // TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a + // single RecyclerView once this screen is tested by screenshot tests. Introduce a + // PeopleSpaceTileViewBinder that will properly create and bind the View associated to a + // PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class). + val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations) + setTileViews( + conversationsView, + R.id.priority, + R.id.priority_tiles, + priorityTiles, + onTileClicked, + ) + + setTileViews( + conversationsView, + R.id.recent, + R.id.recent_tiles, + recentTiles, + onTileClicked, + ) + } + + /** Sets a [PeopleSpaceTileView]s for each conversation. */ + private fun setTileViews( + root: View, + tilesListId: Int, + tilesId: Int, + tiles: List<PeopleTileViewModel>, + onTileClicked: (PeopleTileViewModel) -> Unit, + ) { + // Remove any previously added tile. + // TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use + // DiffUtil to do as less addView/removeView as possible. + val layout = root.requireViewById<ViewGroup>(tilesId) + layout.removeAllViews() + layout.outlineProvider = ViewOutlineProvider + + val tilesListView = root.requireViewById<LinearLayout>(tilesListId) + if (tiles.isEmpty()) { + tilesListView.visibility = View.GONE + return + } + tilesListView.visibility = View.VISIBLE + + // Add each tile. + tiles.forEachIndexed { i, tile -> + val tileView = + PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1) + bindTileView(tileView, tile, onTileClicked) + } + } + + /** Sets [tileView] with the data in [conversation]. */ + private fun bindTileView( + tileView: PeopleSpaceTileView, + tile: PeopleTileViewModel, + onTileClicked: (PeopleTileViewModel) -> Unit, + ) { + try { + tileView.setName(tile.username) + tileView.setPersonIcon(tile.icon) + tileView.setOnClickListener { onTileClicked(tile) } + } catch (e: Exception) { + Log.e(TAG, "Couldn't retrieve shortcut information", e) + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt index 60123ab97fd7..40205ce9424a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java +++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,23 +14,14 @@ * limitations under the License. */ -package com.android.wm.shell.hidedisplaycutout; +package com.android.systemui.people.ui.viewmodel -import android.content.res.Configuration; +import android.graphics.Bitmap +import com.android.systemui.people.widget.PeopleTileKey -import androidx.annotation.NonNull; - -import com.android.wm.shell.common.annotations.ExternalThread; - -import java.io.PrintWriter; - -/** - * Interface to engage hide display cutout feature. - */ -@ExternalThread -public interface HideDisplayCutout { - /** - * Notifies {@link Configuration} changed. - */ - void onConfigurationChanged(Configuration newConfig); -} +/** Models UI state for a single tile/conversation. */ +data class PeopleTileViewModel( + val key: PeopleTileKey, + val icon: Bitmap, + val username: String?, +) diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt new file mode 100644 index 000000000000..17de991588b8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt @@ -0,0 +1,149 @@ +/* + * 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.people.ui.viewmodel + +import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID +import android.content.Context +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.people.PeopleSpaceUtils +import com.android.systemui.people.PeopleTileViewHelper +import com.android.systemui.people.data.model.PeopleTileModel +import com.android.systemui.people.data.repository.PeopleTileRepository +import com.android.systemui.people.data.repository.PeopleWidgetRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** + * Models UI state for the people space, allowing the user to select which conversation should be + * associated to a new or existing Conversation widget. + */ +class PeopleViewModel( + @Application private val context: Context, + private val tileRepository: PeopleTileRepository, + private val widgetRepository: PeopleWidgetRepository, +) : ViewModel() { + /** + * The list of the priority tiles/conversations. + * + * Important: Even though this is a Flow, the underlying API used to populate this Flow is not + * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. + */ + private val _priorityTiles = MutableStateFlow(priorityTiles()) + val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles + + /** + * The list of the priority tiles/conversations. + * + * Important: Even though this is a Flow, the underlying API used to populate this Flow is not + * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. + */ + private val _recentTiles = MutableStateFlow(recentTiles()) + val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles + + /** The ID of the widget currently being edited/added. */ + private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) + val appWidgetId: StateFlow<Int> = _appWidgetId + + /** Whether the user journey is complete. */ + private val _isFinished = MutableStateFlow(false) + val isFinished: StateFlow<Boolean> = _isFinished + + /** Refresh the [priorityTiles] and [recentTiles]. */ + fun onTileRefreshRequested() { + _priorityTiles.value = priorityTiles() + _recentTiles.value = recentTiles() + } + + /** Called when the [appWidgetId] should be changed to [widgetId]. */ + fun onWidgetIdChanged(widgetId: Int) { + _appWidgetId.value = widgetId + } + + /** Clear [isFinished], setting it to false. */ + fun clearIsFinished() { + _isFinished.value = false + } + + /** Called when a tile is clicked. */ + fun onTileClicked(tile: PeopleTileViewModel) { + if (PeopleSpaceUtils.DEBUG) { + Log.d( + TAG, + "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID: " + + _appWidgetId.value + ) + } + widgetRepository.setWidgetTile(_appWidgetId.value, tile.key) + _isFinished.value = true + } + + private fun priorityTiles(): List<PeopleTileViewModel> { + return try { + tileRepository.priorityTiles().map { it.toViewModel() } + } catch (e: Exception) { + Log.e(TAG, "Couldn't retrieve priority conversations", e) + emptyList() + } + } + + private fun recentTiles(): List<PeopleTileViewModel> { + return try { + tileRepository.recentTiles().map { it.toViewModel() } + } catch (e: Exception) { + Log.e(TAG, "Couldn't retrieve recent conversations", e) + emptyList() + } + } + + private fun PeopleTileModel.toViewModel(): PeopleTileViewModel { + val icon = + PeopleTileViewHelper.getPersonIconBitmap( + context, + this, + PeopleTileViewHelper.getSizeInDp( + context, + R.dimen.avatar_size_for_medium, + context.resources.displayMetrics.density, + ) + ) + return PeopleTileViewModel(key, icon, username) + } + + /** The Factory that should be used to create a [PeopleViewModel]. */ + class Factory + @Inject + constructor( + @Application private val context: Context, + private val tileRepository: PeopleTileRepository, + private val widgetRepository: PeopleWidgetRepository, + ) : ViewModelProvider.Factory { + override fun <T : ViewModel> create(modelClass: Class<T>): T { + check(modelClass == PeopleViewModel::class.java) + return PeopleViewModel(context, tileRepository, widgetRepository) as T + } + } + + companion object { + private const val TAG = "PeopleSpaceViewModel" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java index e1b97a454c5d..20c6c556706e 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.os.Bundle; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; @@ -34,6 +35,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -43,6 +45,7 @@ import com.android.systemui.wmshell.BubblesManager; import com.android.wm.shell.bubbles.Bubble; import java.util.Optional; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -58,6 +61,7 @@ public class LaunchConversationActivity extends Activity { private boolean mIsForTesting; private IStatusBarService mIStatusBarService; private CommandQueue mCommandQueue; + private Executor mBgExecutor; private Bubble mBubble; private NotificationEntry mEntryToBubble; @@ -67,7 +71,8 @@ public class LaunchConversationActivity extends Activity { CommonNotifCollection commonNotifCollection, Optional<BubblesManager> bubblesManagerOptional, UserManager userManager, - CommandQueue commandQueue + CommandQueue commandQueue, + @Background Executor bgExecutor ) { super(); mVisibilityProvider = visibilityProvider; @@ -91,6 +96,7 @@ public class LaunchConversationActivity extends Activity { mCommandQueue.removeCallback(this); } }); + mBgExecutor = bgExecutor; } @Override @@ -172,34 +178,36 @@ public class LaunchConversationActivity extends Activity { return; } - try { - if (mIStatusBarService == null || mCommonNotifCollection == null) { - if (DEBUG) { - Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey); - } - return; + if (mIStatusBarService == null || mCommonNotifCollection == null) { + if (DEBUG) { + Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey); } + return; + } - NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey); - if (entry == null || entry.getRanking() == null) { - if (DEBUG) { - Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking" - + " is null, key: " + notifKey); - } - return; + NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey); + if (entry == null || entry.getRanking() == null) { + if (DEBUG) { + Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking" + + " is null, key: " + notifKey); } + return; + } - NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true); - int rank = notifVisibility.rank; + NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true); + int rank = notifVisibility.rank; - if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank); - mIStatusBarService.onNotificationClear( - packageName, userHandle.getIdentifier(), notifKey, - NotificationStats.DISMISSAL_OTHER, - NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility); - } catch (Exception e) { - Log.e(TAG, "Exception cancelling notification:" + e); - } + if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank); + mBgExecutor.execute(() -> { + try { + mIStatusBarService.onNotificationClear( + packageName, userHandle.getIdentifier(), notifKey, + NotificationStats.DISMISSAL_OTHER, + NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility); + } catch (RemoteException e) { + Log.e(TAG, "Exception cancelling notification:" + e); + } + }); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 6c58c459c2c2..438236d6a63d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -51,7 +51,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; @@ -97,6 +96,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.buttons.KeyButtonView; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.settings.CurrentUserTracker; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.model.Task; @@ -105,7 +105,6 @@ import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; import com.android.wm.shell.back.BackAnimation; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 5bb3413595ba..a837cbb8a50e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -29,6 +29,7 @@ import android.graphics.drawable.Icon; import android.media.MediaRecorder; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; @@ -40,6 +41,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.LongRunning; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; @@ -51,9 +54,10 @@ import javax.inject.Inject; /** * A service which records the device screen and optionally microphone input. */ -public class RecordingService extends Service implements MediaRecorder.OnInfoListener { +public class RecordingService extends Service implements ScreenMediaRecorderListener { public static final int REQUEST_CODE = 2; + private static final int USER_ID_NOT_SPECIFIED = -1; private static final int NOTIFICATION_RECORDING_ID = 4274; private static final int NOTIFICATION_PROCESSING_ID = 4275; private static final int NOTIFICATION_VIEW_ID = 4273; @@ -73,6 +77,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis private final RecordingController mController; private final KeyguardDismissUtil mKeyguardDismissUtil; + private final Handler mMainHandler; private ScreenRecordingAudioSource mAudioSource; private boolean mShowTaps; private boolean mOriginalShowTaps; @@ -84,10 +89,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis @Inject public RecordingService(RecordingController controller, @LongRunning Executor executor, - UiEventLogger uiEventLogger, NotificationManager notificationManager, + @Main Handler handler, UiEventLogger uiEventLogger, + NotificationManager notificationManager, UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) { mController = controller; mLongExecutor = executor; + mMainHandler = handler; mUiEventLogger = uiEventLogger; mNotificationManager = notificationManager; mUserContextTracker = userContextTracker; @@ -138,6 +145,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis mRecorder = new ScreenMediaRecorder( mUserContextTracker.getUserContext(), + mMainHandler, currentUserId, mAudioSource, this @@ -166,14 +174,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis } // Check user ID - we may be getting a stop intent after user switch, in which case // we want to post the notifications for that user, which is NOT current user - int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (userId == -1) { - userId = mUserContextTracker.getUserContext().getUserId(); - } - Log.d(TAG, "notifying for user " + userId); - stopRecording(userId); - mNotificationManager.cancel(NOTIFICATION_RECORDING_ID); - stopSelf(); + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED); + stopService(userId); break; case ACTION_SHARE: @@ -378,15 +380,39 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis return builder.build(); } - private void stopRecording(int userId) { + private void stopService() { + stopService(USER_ID_NOT_SPECIFIED); + } + + private void stopService(int userId) { + if (userId == USER_ID_NOT_SPECIFIED) { + userId = mUserContextTracker.getUserContext().getUserId(); + } + Log.d(TAG, "notifying for user " + userId); setTapsVisible(mOriginalShowTaps); if (getRecorder() != null) { - getRecorder().end(); - saveRecording(userId); + try { + getRecorder().end(); + saveRecording(userId); + } catch (RuntimeException exception) { + // RuntimeException could happen if the recording stopped immediately after starting + // let's release the recorder and delete all temporary files in this case + getRecorder().release(); + showErrorToast(R.string.screenrecord_start_error); + Log.e(TAG, "stopRecording called, but there was an error when ending" + + "recording"); + exception.printStackTrace(); + } catch (Throwable throwable) { + // Something unexpected happen, SystemUI will crash but let's delete + // the temporary files anyway + getRecorder().release(); + throw new RuntimeException(throwable); + } } else { Log.e(TAG, "stopRecording called, but recorder was null"); } updateState(false); + stopSelf(); } private void saveRecording(int userId) { @@ -446,4 +472,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis Log.d(TAG, "Media recorder info: " + what); onStartCommand(getStopIntent(this), 0, 0); } + + @Override + public void onStopped() { + if (mController.isRecording()) { + Log.d(TAG, "Stopping recording because the system requested the stop"); + stopService(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index 2133cf63d1c3..d098b4b3442a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -40,6 +40,7 @@ import android.media.projection.IMediaProjectionManager; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.net.Uri; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -51,16 +52,19 @@ import android.view.Surface; import android.view.WindowManager; import java.io.File; +import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; /** * Recording screen and mic/internal audio */ -public class ScreenMediaRecorder { +public class ScreenMediaRecorder extends MediaProjection.Callback { private static final int TOTAL_NUM_TRACKS = 1; private static final int VIDEO_FRAME_RATE = 30; private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6; @@ -81,14 +85,16 @@ public class ScreenMediaRecorder { private ScreenRecordingMuxer mMuxer; private ScreenInternalAudioRecorder mAudio; private ScreenRecordingAudioSource mAudioSource; + private final Handler mHandler; private Context mContext; - MediaRecorder.OnInfoListener mListener; + ScreenMediaRecorderListener mListener; - public ScreenMediaRecorder(Context context, + public ScreenMediaRecorder(Context context, Handler handler, int user, ScreenRecordingAudioSource audioSource, - MediaRecorder.OnInfoListener listener) { + ScreenMediaRecorderListener listener) { mContext = context; + mHandler = handler; mUser = user; mListener = listener; mAudioSource = audioSource; @@ -105,6 +111,7 @@ public class ScreenMediaRecorder { IBinder projection = proj.asBinder(); mMediaProjection = new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection)); + mMediaProjection.registerCallback(this, mHandler); File cacheDir = mContext.getCacheDir(); cacheDir.mkdirs(); @@ -162,10 +169,15 @@ public class ScreenMediaRecorder { metrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mInputSurface, - null, - null); - - mMediaRecorder.setOnInfoListener(mListener); + new VirtualDisplay.Callback() { + @Override + public void onStopped() { + onStop(); + } + }, + mHandler); + + mMediaRecorder.setOnInfoListener((mr, what, extra) -> mListener.onInfo(mr, what, extra)); if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) { mTempAudioFile = File.createTempFile("temp", ".aac", @@ -259,21 +271,34 @@ public class ScreenMediaRecorder { } /** - * End screen recording + * End screen recording, throws an exception if stopping recording failed */ - void end() { - mMediaRecorder.stop(); - mMediaRecorder.release(); - mInputSurface.release(); - mVirtualDisplay.release(); - mMediaProjection.stop(); + void end() throws IOException { + Closer closer = new Closer(); + + // MediaRecorder might throw RuntimeException if stopped immediately after starting + // We should remove the recording in this case as it will be invalid + closer.register(mMediaRecorder::stop); + closer.register(mMediaRecorder::release); + closer.register(mInputSurface::release); + closer.register(mVirtualDisplay::release); + closer.register(mMediaProjection::stop); + closer.register(this::stopInternalAudioRecording); + + closer.close(); + mMediaRecorder = null; mMediaProjection = null; - stopInternalAudioRecording(); Log.d(TAG, "end recording"); } + @Override + public void onStop() { + Log.d(TAG, "The system notified about stopping the projection"); + mListener.onStopped(); + } + private void stopInternalAudioRecording() { if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) { mAudio.end(); @@ -337,6 +362,18 @@ public class ScreenMediaRecorder { } /** + * Release the resources without saving the data + */ + protected void release() { + if (mTempVideoFile != null) { + mTempVideoFile.delete(); + } + if (mTempAudioFile != null) { + mTempAudioFile.delete(); + } + } + + /** * Object representing the recording */ public class SavedRecording { @@ -362,4 +399,66 @@ public class ScreenMediaRecorder { return mThumbnailBitmap; } } + + interface ScreenMediaRecorderListener { + /** + * Called to indicate an info or a warning during recording. + * See {@link MediaRecorder.OnInfoListener} for the full description. + */ + void onInfo(MediaRecorder mr, int what, int extra); + + /** + * Called when the recording stopped by the system. + * For example, this might happen when doing partial screen sharing of an app + * and the app that is being captured is closed. + */ + void onStopped(); + } + + /** + * Allows to register multiple {@link Closeable} objects and close them all by calling + * {@link Closer#close}. If there is an exception thrown during closing of one + * of the registered closeables it will continue trying closing the rest closeables. + * If there are one or more exceptions thrown they will be re-thrown at the end. + * In case of multiple exceptions only the first one will be thrown and all the rest + * will be printed. + */ + private static class Closer implements Closeable { + private final List<Closeable> mCloseables = new ArrayList<>(); + + void register(Closeable closeable) { + mCloseables.add(closeable); + } + + @Override + public void close() throws IOException { + Throwable throwable = null; + + for (int i = 0; i < mCloseables.size(); i++) { + Closeable closeable = mCloseables.get(i); + + try { + closeable.close(); + } catch (Throwable e) { + if (throwable == null) { + throwable = e; + } else { + e.printStackTrace(); + } + } + } + + if (throwable != null) { + if (throwable instanceof IOException) { + throw (IOException) throwable; + } + + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + throw (Error) throwable; + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt index d44a56942065..07e8b9fe3123 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt @@ -11,7 +11,7 @@ * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ -package com.android.systemui.statusbar.phone +package com.android.systemui.shade import android.view.MotionEvent import com.android.systemui.dump.DumpsysTableLogger diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt index a385e22c1aff..ce9d89f89ae1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone +package com.android.systemui.shade /** Provides certain notification panel events. */ interface NotifPanelEvents { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java index 2aaf6a5f4391..67723843086a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEventsModule.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import com.android.systemui.dagger.SysUISingleton; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt index ff48755f750a..e0cd482166b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone +package com.android.systemui.shade import android.content.Context import android.view.ViewGroup diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java index d9ba494a4d63..e0997ff0f905 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import android.content.Context; import android.graphics.Canvas; @@ -25,6 +25,8 @@ import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.PanelView; +import com.android.systemui.statusbar.phone.TapAgainView; public class NotificationPanelView extends PanelView { @@ -35,8 +37,8 @@ public class NotificationPanelView extends PanelView { */ public static final int FLING_EXPAND = 0; - static final String COUNTER_PANEL_OPEN = "panel_open"; - static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; + public static final String COUNTER_PANEL_OPEN = "panel_open"; + public static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; private int mCurrentPanelAlpha; private final Paint mAlphaPaint = new Paint(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index f401bb4629c1..bab92ba492c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; @@ -140,6 +140,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.GestureRecorder; @@ -176,12 +177,35 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; +import com.android.systemui.statusbar.phone.KeyguardBouncer; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm; +import com.android.systemui.statusbar.phone.KeyguardStatusBarView; +import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; +import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; +import com.android.systemui.statusbar.phone.NotificationIconAreaController; +import com.android.systemui.statusbar.phone.PanelView; +import com.android.systemui.statusbar.phone.PanelViewController; +import com.android.systemui.statusbar.phone.PhoneStatusBarView; +import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; +import com.android.systemui.statusbar.phone.TapAgainViewController; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.phone.panelstate.PanelState; -import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -1679,11 +1703,16 @@ public final class NotificationPanelViewController extends PanelViewController { setQsExpansion(mQsMinExpansionHeight); } + @Override + @VisibleForTesting + protected void cancelHeightAnimator() { + super.cancelHeightAnimator(); + } + public void cancelAnimation() { mView.animate().cancel(); } - /** * Animate QS closing by flinging it. * If QS is expanded, it will collapse into QQS and stop. @@ -2977,7 +3006,7 @@ public final class NotificationPanelViewController extends PanelViewController { } @Override - protected int getMaxPanelHeight() { + public int getMaxPanelHeight() { int min = mStatusBarMinHeight; if (!(mBarState == KEYGUARD) && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) { @@ -3081,7 +3110,7 @@ public final class NotificationPanelViewController extends PanelViewController { } } - boolean isPanelExpanded() { + public boolean isPanelExpanded() { return mPanelExpanded; } @@ -4874,6 +4903,16 @@ public final class NotificationPanelViewController extends PanelViewController { return mStatusBarViewTouchEventHandler; } + @VisibleForTesting + StatusBarStateController getStatusBarStateController() { + return mStatusBarStateController; + } + + @VisibleForTesting + boolean isHintAnimationRunning() { + return mHintAnimationRunning; + } + private void onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState int state) { if (state != WINDOW_STATE_SHOWING && mStatusBarStateController.getState() == StatusBarState.SHADE) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index 1e3a02b0606b..121d69d34678 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import static android.view.WindowInsets.Type.systemBars; @@ -52,6 +52,7 @@ import android.widget.FrameLayout; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.CentralSurfaces; /** * Combined keyguard and notification panel view. Also holding backdrop and scrims. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index be5b33eb0da0..b8546df75f5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import android.app.StatusBarManager; import android.hardware.display.AmbientDisplayConfiguration; @@ -46,6 +46,9 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.window.StatusBarWindowStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index ebedbf987aa2..13a5615a8b54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone +package com.android.systemui.shade import android.view.View import android.view.ViewGroup @@ -6,11 +6,7 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowInsets import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM -import androidx.constraintlayout.widget.ConstraintSet.END -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import androidx.constraintlayout.widget.ConstraintSet.START -import androidx.constraintlayout.widget.ConstraintSet.TOP +import androidx.constraintlayout.widget.ConstraintSet.* import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java index 2446cf7ba412..587e0e6dd834 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import android.app.Fragment; import android.content.Context; diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS new file mode 100644 index 000000000000..133711ee5ab2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS @@ -0,0 +1,13 @@ +per-file *Notification* = set noparent +per-file *Notification* = file:../statusbar/notification/OWNERS + +per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com +per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com + +per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com +per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com + +per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com + +per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com +per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt index 2789db874249..f4db3ab9289b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone.shade.transition +package com.android.systemui.shade.transition import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt index 1b8afb9ddc1d..afd57daca10b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone.shade.transition +package com.android.systemui.shade.transition import android.content.res.Configuration import android.content.res.Resources diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt index f1cedeb21e0a..6c3a028c2380 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone.shade.transition +package com.android.systemui.shade.transition import com.android.systemui.statusbar.phone.panelstate.PanelState diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt index 71c61597ff11..58acfb40ee44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone.shade.transition +package com.android.systemui.shade.transition import android.content.Context import android.content.res.Configuration @@ -6,8 +6,10 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.qs.QS +import com.android.systemui.shade.NotificationPanelViewController +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.statusbar.phone.NotificationPanelViewController import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager import com.android.systemui.statusbar.phone.panelstate.PanelState @@ -27,7 +29,8 @@ constructor( private val context: Context, private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory, private val noOpOverScroller: NoOpOverScroller, - private val scrimShadeTransitionController: ScrimShadeTransitionController + private val scrimShadeTransitionController: ScrimShadeTransitionController, + private val statusBarStateController: SysuiStatusBarStateController, ) { lateinit var notificationPanelViewController: NotificationPanelViewController @@ -43,7 +46,7 @@ constructor( } private val shadeOverScroller: ShadeOverScroller get() = - if (inSplitShade && propertiesInitialized()) { + if (inSplitShade && isScreenUnlocked() && propertiesInitialized()) { splitShadeOverScroller } else { noOpOverScroller @@ -90,6 +93,7 @@ constructor( """ ShadeTransitionController: inSplitShade: $inSplitShade + isScreenUnlocked: ${isScreenUnlocked()} currentPanelState: ${currentPanelState?.panelStateToString()} lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent qs.isInitialized: ${this::qs.isInitialized} @@ -97,4 +101,7 @@ constructor( nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized} """.trimIndent()) } + + private fun isScreenUnlocked() = + statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt index c25aab8eb80f..204dd3c07d8e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone.shade.transition +package com.android.systemui.shade.transition import android.animation.Animator import android.animation.ValueAnimator diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt index 01eb4446ea8e..886ad684649f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt @@ -6,7 +6,7 @@ import android.util.MathUtils import com.android.systemui.R import com.android.systemui.dump.DumpManager import com.android.systemui.media.MediaHierarchyManager -import com.android.systemui.statusbar.phone.NotificationPanelViewController +import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.statusbar.policy.ConfigurationController import dagger.assisted.Assisted import dagger.assisted.AssistedFactory diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 9d80d5fbc2ca..74d8f1beaec1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -29,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QS import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView @@ -37,7 +38,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.LSShadeTransitionLogger -import com.android.systemui.statusbar.phone.NotificationPanelViewController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.LargeScreenUtils import java.io.PrintWriter diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 587c23b39dd3..9e77dbc08c7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl; +import com.android.systemui.statusbar.phone.StatusBarIconList; import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags; @@ -256,6 +257,16 @@ public interface CentralSurfacesDependenciesModule { */ @Provides @SysUISingleton + static StatusBarIconList provideStatusBarIconList(Context context) { + return new StatusBarIconList( + context.getResources().getStringArray( + com.android.internal.R.array.config_statusBarIcons)); + } + + /** + */ + @Provides + @SysUISingleton static OngoingCallController provideOngoingCallController( Context context, CommonNotifCollection notifCollection, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index c86bf93628dd..852d5f7a16e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -41,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dumpable; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationListener; @@ -73,6 +74,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Executor; import dagger.Lazy; @@ -113,6 +115,7 @@ public class NotificationEntryManager implements private final IStatusBarService mStatusBarService; private final NotifLiveDataStoreImpl mNotifLiveDataStore; private final DumpManager mDumpManager; + private final Executor mBgExecutor; private final Set<NotificationEntry> mAllNotifications = new ArraySet<>(); private final Set<NotificationEntry> mReadOnlyAllNotifications = @@ -159,7 +162,8 @@ public class NotificationEntryManager implements LeakDetector leakDetector, IStatusBarService statusBarService, NotifLiveDataStoreImpl notifLiveDataStore, - DumpManager dumpManager + DumpManager dumpManager, + @Background Executor bgExecutor ) { mLogger = logger; mGroupManager = groupManager; @@ -170,6 +174,7 @@ public class NotificationEntryManager implements mStatusBarService = statusBarService; mNotifLiveDataStore = notifLiveDataStore; mDumpManager = dumpManager; + mBgExecutor = bgExecutor; } /** Once called, the NEM will start processing notification events from system server. */ @@ -566,17 +571,19 @@ public class NotificationEntryManager implements private void sendNotificationRemovalToServer( StatusBarNotification notification, DismissedByUserStats dismissedByUserStats) { - try { - mStatusBarService.onNotificationClear( - notification.getPackageName(), - notification.getUser().getIdentifier(), - notification.getKey(), - dismissedByUserStats.dismissalSurface, - dismissedByUserStats.dismissalSentiment, - dismissedByUserStats.notificationVisibility); - } catch (RemoteException ex) { - // system process is dead if we're here. - } + mBgExecutor.execute(() -> { + try { + mStatusBarService.onNotificationClear( + notification.getPackageName(), + notification.getUser().getIdentifier(), + notification.getKey(), + dismissedByUserStats.dismissalSurface, + dismissedByUserStats.dismissalSentiment, + dismissedByUserStats.notificationVisibility); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + }); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt index b764c271de45..31b21c9b5321 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -4,10 +4,10 @@ import android.view.ViewGroup import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.LaunchAnimator +import com.android.systemui.shade.NotificationShadeWindowViewController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.HeadsUpManagerPhone -import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent import com.android.systemui.statusbar.policy.HeadsUpUtil import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 2c85fddc2905..351a4bea2947 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -70,6 +70,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.LogBufferEulogizer; @@ -112,6 +113,7 @@ import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -146,6 +148,7 @@ public class NotifCollection implements Dumpable { private final NotifPipelineFlags mNotifPipelineFlags; private final NotifCollectionLogger mLogger; private final Handler mMainHandler; + private final Executor mBgExecutor; private final LogBufferEulogizer mEulogizer; private final DumpManager mDumpManager; @@ -174,6 +177,7 @@ public class NotifCollection implements Dumpable { NotifPipelineFlags notifPipelineFlags, NotifCollectionLogger logger, @Main Handler mainHandler, + @Background Executor bgExecutor, LogBufferEulogizer logBufferEulogizer, DumpManager dumpManager) { mStatusBarService = statusBarService; @@ -181,6 +185,7 @@ public class NotifCollection implements Dumpable { mNotifPipelineFlags = notifPipelineFlags; mLogger = logger; mMainHandler = mainHandler; + mBgExecutor = bgExecutor; mEulogizer = logBufferEulogizer; mDumpManager = dumpManager; } @@ -294,18 +299,20 @@ public class NotifCollection implements Dumpable { entriesToLocallyDismiss.add(entry); if (!isCanceled(entry)) { // send message to system server if this notification hasn't already been cancelled - try { - mStatusBarService.onNotificationClear( - entry.getSbn().getPackageName(), - entry.getSbn().getUser().getIdentifier(), - entry.getSbn().getKey(), - stats.dismissalSurface, - stats.dismissalSentiment, - stats.notificationVisibility); - } catch (RemoteException e) { - // system process is dead if we're here. - mLogger.logRemoteExceptionOnNotificationClear(entry, e); - } + mBgExecutor.execute(() -> { + try { + mStatusBarService.onNotificationClear( + entry.getSbn().getPackageName(), + entry.getSbn().getUser().getIdentifier(), + entry.getSbn().getKey(), + stats.dismissalSurface, + stats.dismissalSentiment, + stats.notificationVisibility); + } catch (RemoteException e) { + // system process is dead if we're here. + mLogger.logRemoteExceptionOnNotificationClear(entry, e); + } + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 0833df505bf3..d3bc257d8a54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -28,13 +28,13 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotifPanelEvents; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; -import com.android.systemui.statusbar.phone.NotifPanelEvents; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Compile; import com.android.systemui.util.concurrency.DelayableExecutor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index d838252aa362..bf08fc7f53e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.shade.NotifPanelEventsModule; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.AssistantFeedbackController; @@ -84,7 +85,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationSectionsMan import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotifPanelEventsModule; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.wmshell.BubblesManager; @@ -128,7 +128,8 @@ public interface NotificationsModule { LeakDetector leakDetector, IStatusBarService statusBarService, NotifLiveDataStoreImpl notifLiveDataStore, - DumpManager dumpManager) { + DumpManager dumpManager, + @Background Executor bgExecutor) { return new NotificationEntryManager( logger, groupManager, @@ -138,7 +139,8 @@ public interface NotificationsModule { leakDetector, statusBarService, notifLiveDataStore, - dumpManager); + dumpManager, + bgExecutor); } /** Provides an instance of {@link NotificationGutsManager} */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 016b388ff60a..99d320d1c7ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.interruption import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotificationInterruptLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey @@ -211,6 +212,33 @@ class NotificationInterruptLogger @Inject constructor( }) } + fun logNoFullscreen(entry: NotificationEntry, reason: String) { + buffer.log(TAG, DEBUG, { + str1 = entry.logKey + str2 = reason + }, { + "No FullScreenIntent: $str2: $str1" + }) + } + + fun logNoFullscreenWarning(entry: NotificationEntry, reason: String) { + buffer.log(TAG, WARNING, { + str1 = entry.logKey + str2 = reason + }, { + "No FullScreenIntent: WARNING: $str2: $str1" + }) + } + + fun logFullscreen(entry: NotificationEntry, reason: String) { + buffer.log(TAG, DEBUG, { + str1 = entry.logKey + str2 = reason + }, { + "FullScreenIntent: $str2: $str1" + }) + } + fun keyguardHideNotification(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 2dd95a3cbab8..535dc6e2b583 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -177,9 +177,69 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter */ @Override public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) { - return entry.getSbn().getNotification().fullScreenIntent != null - && (!shouldHeadsUp(entry) - || mStatusBarStateController.getState() == StatusBarState.KEYGUARD); + if (entry.getSbn().getNotification().fullScreenIntent == null) { + return false; + } + + // Never show FSI when suppressed by DND + if (entry.shouldSuppressFullScreenIntent()) { + mLogger.logNoFullscreen(entry, "Suppressed by DND"); + return false; + } + + // Never show FSI if importance is not HIGH + if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { + mLogger.logNoFullscreen(entry, "Not important enough"); + return false; + } + + // If the notification has suppressive GroupAlertBehavior, block FSI and warn. + StatusBarNotification sbn = entry.getSbn(); + if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { + // b/231322873: Detect and report an event when a notification has both an FSI and a + // suppressive groupAlertBehavior, and now correctly block the FSI from firing. + final int uid = entry.getSbn().getUid(); + android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior"); + mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN"); + return false; + } + + // If the screen is off, then launch the FullScreenIntent + if (!mPowerManager.isInteractive()) { + mLogger.logFullscreen(entry, "Device is not interactive"); + return true; + } + + // If the device is currently dreaming, then launch the FullScreenIntent + if (isDreaming()) { + mLogger.logFullscreen(entry, "Device is dreaming"); + return true; + } + + // If the keyguard is showing, then launch the FullScreenIntent + if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { + mLogger.logFullscreen(entry, "Keyguard is showing"); + return true; + } + + // If the notification should HUN, then we don't need FSI + if (shouldHeadsUp(entry)) { + mLogger.logNoFullscreen(entry, "Expected to HUN"); + return false; + } + + // If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI. + mLogger.logFullscreen(entry, "Expected not to HUN"); + return true; + } + + private boolean isDreaming() { + try { + return mDreamManager.isDreaming(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to query dream manager.", e); + return false; + } } private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) { @@ -223,13 +283,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } - boolean isDreaming = false; - try { - isDreaming = mDreamManager.isDreaming(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to query dream manager.", e); - } - boolean inUse = mPowerManager.isScreenOn() && !isDreaming; + boolean inUse = mPowerManager.isScreenOn() && !isDreaming(); if (!inUse) { mLogger.logNoHeadsUpNotInUse(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 1182fb370280..596b767bb434 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -69,6 +69,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; @@ -110,7 +111,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; -import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.DeviceProvisionedController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 3d29897aec8a..5a8050814c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -47,6 +47,9 @@ import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.NotificationPresenter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index cb9afe887ab7..53b5b0caf257 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -55,6 +55,9 @@ import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.shade.NotificationPanelView; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 2cad6839069c..2d6d846bf2a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -174,6 +174,9 @@ import com.android.systemui.qs.QSPanelController; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.BackDropView; @@ -1706,7 +1709,7 @@ public class CentralSurfacesImpl extends CoreStartable implements // Wrap the animation controller to dismiss the shade and set // mIsLaunchingActivityOverLockscreen during the animation. ActivityLaunchAnimator.Controller delegate = wrapAnimationController( - animationController, dismissShade); + animationController, dismissShade, /* isLaunchForActivity= */ true); controller = new DelegateLaunchAnimatorController(delegate) { @Override public void onIntentStarted(boolean willAnimate) { @@ -2454,7 +2457,7 @@ public class CentralSurfacesImpl extends CoreStartable implements true /* isActivityIntent */); ActivityLaunchAnimator.Controller animController = animationController != null ? wrapAnimationController(animationController, - dismissShade) : null; + dismissShade, /* isLaunchForActivity= */ true) : null; // If we animate, we will dismiss the shade only once the animation is done. This is taken // care of by the StatusBarLaunchAnimationController. @@ -2532,9 +2535,25 @@ public class CentralSurfacesImpl extends CoreStartable implements willLaunchResolverActivity, deferred /* deferred */, animate); } + /** + * Return a {@link ActivityLaunchAnimator.Controller} wrapping {@code animationController} so + * that: + * - if it launches in the notification shade window and {@code dismissShade} is true, then + * the shade will be instantly dismissed at the end of the animation. + * - if it launches in status bar window, it will make the status bar window match the device + * size during the animation (that way, the animation won't be clipped by the status bar + * size). + * + * @param animationController the controller that is wrapped and will drive the main animation. + * @param dismissShade whether the notification shade will be dismissed at the end of the + * animation. This is ignored if {@code animationController} is not + * animating in the shade window. + * @param isLaunchForActivity whether the launch is for an activity. + */ @Nullable private ActivityLaunchAnimator.Controller wrapAnimationController( - ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { + ActivityLaunchAnimator.Controller animationController, boolean dismissShade, + boolean isLaunchForActivity) { View rootView = animationController.getLaunchContainer().getRootView(); Optional<ActivityLaunchAnimator.Controller> controllerFromStatusBar = @@ -2548,7 +2567,7 @@ public class CentralSurfacesImpl extends CoreStartable implements // If the view is not in the status bar, then we are animating a view in the shade. // We have to make sure that we collapse it when the animation ends or is cancelled. return new StatusBarLaunchAnimatorController(animationController, this, - true /* isLaunchForActivity */); + isLaunchForActivity); } return animationController; @@ -4080,8 +4099,9 @@ public class CentralSurfacesImpl extends CoreStartable implements // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the // shade is collapsed after the animation (or when it is cancelled, aborted, etc). ActivityLaunchAnimator.Controller controller = - animationController != null ? new StatusBarLaunchAnimatorController( - animationController, this, intent.isActivity()) : null; + animationController != null ? wrapAnimationController( + animationController, /* dismissShade= */ true, intent.isActivity()) + : null; mActivityLaunchAnimator.startPendingIntentWithAnimation( controller, animate, intent.getCreatorPackage(), 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 55b310ff986d..80c3e6ce989d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -40,6 +40,8 @@ import com.android.systemui.doze.DozeLog; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 9863a0ed1ce0..484441a1e76b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -26,6 +26,7 @@ import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.HeadsUpStatusBarView; @@ -40,8 +41,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.util.ViewController; -import java.util.Optional; import java.util.ArrayList; +import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 415bd90ed23a..80432dbd277c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -217,8 +217,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, * Notify that the status bar panel gets expanded or collapsed. * * @param isExpanded True to notify expanded, false to notify collapsed. + * TODO(b/237811427) replace with a listener */ - void setIsPanelExpanded(boolean isExpanded) { + public void setIsPanelExpanded(boolean isExpanded) { if (isExpanded != mIsExpanded) { mIsExpanded = isExpanded; if (isExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index 42f301d2f222..6bfb0dad28f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -21,6 +21,7 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index dde6b168b350..01af48616b65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -27,6 +27,7 @@ import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.keyguard.KeyguardStatusView; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index def574c1890f..f06b346780da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -42,6 +42,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 2dc3261eb886..a2140c6ab6b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License */ - package com.android.systemui.statusbar.phone; import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY; @@ -31,6 +30,7 @@ import android.util.MathUtils; import android.util.Property; import android.view.ContextThemeWrapper; import android.view.View; +import android.view.ViewGroup; import android.view.animation.Interpolator; import androidx.annotation.VisibleForTesting; @@ -40,7 +40,6 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; -import com.android.systemui.statusbar.AlphaOptimizedFrameLayout; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.stack.AnimationFilter; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -54,7 +53,7 @@ import java.util.function.Consumer; * A container for notification icons. It handles overflowing icons properly and positions them * correctly on the screen. */ -public class NotificationIconContainer extends AlphaOptimizedFrameLayout { +public class NotificationIconContainer extends ViewGroup { /** * A float value indicating how much before the overflow start the icons should transform into * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts @@ -232,6 +231,31 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } @Override + public boolean hasOverlappingRendering() { + // Does the same as "AlphaOptimizedFrameLayout". + return false; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int childCount = getChildCount(); + final int maxVisibleIcons = getMaxVisibleIcons(childCount); + final int width = MeasureSpec.getSize(widthMeasureSpec); + final int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED); + int totalWidth = (int) (getActualPaddingStart() + getActualPaddingEnd()); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + measureChild(child, childWidthSpec, heightMeasureSpec); + if (i <= maxVisibleIcons) { + totalWidth += child.getMeasuredWidth(); + } + } + final int measuredWidth = resolveSize(totalWidth, widthMeasureSpec); + final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { float centerY = getHeight() / 2.0f; // we layout all our children on the left at the top @@ -408,8 +432,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { float translationX = getActualPaddingStart(); int firstOverflowIndex = -1; int childCount = getChildCount(); - int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD : - mIsStaticLayout ? MAX_STATIC_ICONS : childCount; + int maxVisibleIcons = getMaxVisibleIcons(childCount); float layoutEnd = getLayoutEnd(); mVisualOverflowStart = 0; mFirstVisibleIconState = null; @@ -493,6 +516,11 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } } + private int getMaxVisibleIcons(int childCount) { + return mOnLockScreen ? MAX_ICONS_ON_AOD : + mIsStaticLayout ? MAX_STATIC_ICONS : childCount; + } + private float getLayoutEnd() { return getActualWidth() - getActualPaddingEnd(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS index f5828f914eab..4657e9b84550 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS @@ -3,14 +3,4 @@ per-file *Notification* = file:../notification/OWNERS per-file NotificationIcon* = ccassidy@google.com, evanlaird@google.com, pixel@google.com -per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com -per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com - per-file NotificationShadeWindowControllerImpl.java = dupin@google.com, cinek@google.com, beverlyt@google.com, pixel@google.com, juliacr@google.com -per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com -per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com - -per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com - -per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com -per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index dabc52663a6e..3a85a3ea6391 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -717,7 +717,7 @@ public abstract class PanelViewController { animator.start(); } - void onFlingEnd(boolean cancelled) { + protected void onFlingEnd(boolean cancelled) { mIsFlinging = false; // No overshoot when the animation ends setOverExpansionInternal(0, false /* isFromGesture */); @@ -1113,7 +1113,7 @@ public abstract class PanelViewController { } /** Returns true if {@link PanelView} should be visible. */ - abstract boolean shouldPanelBeVisible(); + abstract protected boolean shouldPanelBeVisible(); /** * Updates the panel expansion and {@link PanelView} visibility if necessary. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java index 2052ee6cdac5..15c6dcfd7889 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java @@ -31,7 +31,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { private final float mIconAlphaWhenOpaque; - private View mLeftSide, mStatusIcons, mBattery; + private View mStartSide, mStatusIcons, mBattery; private Animator mCurrentAnimation; /** @@ -41,7 +41,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { super(backgroundView, R.drawable.status_background); final Resources res = statusBarView.getContext().getResources(); mIconAlphaWhenOpaque = res.getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1); - mLeftSide = statusBarView.findViewById(R.id.status_bar_left_side); + mStartSide = statusBarView.findViewById(R.id.status_bar_start_side_except_heads_up); mStatusIcons = statusBarView.findViewById(R.id.statusIcons); mBattery = statusBarView.findViewById(R.id.battery); applyModeBackground(-1, getMode(), false /*animate*/); @@ -75,7 +75,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { } private void applyMode(int mode, boolean animate) { - if (mLeftSide == null) return; // pre-init + if (mStartSide == null) return; // pre-init float newAlpha = getNonBatteryClockAlphaFor(mode); float newAlphaBC = getBatteryClockAlpha(mode); if (mCurrentAnimation != null) { @@ -84,7 +84,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { if (animate) { AnimatorSet anims = new AnimatorSet(); anims.playTogether( - animateTransitionTo(mLeftSide, newAlpha), + animateTransitionTo(mStartSide, newAlpha), animateTransitionTo(mStatusIcons, newAlpha), animateTransitionTo(mBattery, newAlphaBC) ); @@ -94,7 +94,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { anims.start(); mCurrentAnimation = anims; } else { - mLeftSide.setAlpha(newAlpha); + mStartSide.setAlpha(newAlpha); mStatusIcons.setAlpha(newAlpha); mBattery.setAlpha(newAlphaBC); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 9da2ef734be8..f9c4c8f3b4fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -21,7 +21,6 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver - import com.android.systemui.R import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController @@ -32,9 +31,7 @@ import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.util.ViewController import com.android.systemui.util.kotlin.getOrNull import com.android.systemui.util.view.ViewUtil - import java.util.Optional - import javax.inject.Inject import javax.inject.Named @@ -58,8 +55,8 @@ class PhoneStatusBarViewController private constructor( override fun onViewAttached() { if (moveFromCenterAnimationController == null) return - val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side) - val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area) + val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up) + val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content) val viewsToAnimate = arrayOf( statusBarLeftSide, @@ -126,11 +123,11 @@ class PhoneStatusBarViewController private constructor( class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider { override fun getViewCenter(view: View, outPoint: Point) = when (view.id) { - R.id.status_bar_left_side -> { + R.id.status_bar_start_side_except_heads_up -> { // items aligned to the start, return start center point getViewEdgeCenter(view, outPoint, isStart = true) } - R.id.system_icon_area -> { + R.id.status_bar_end_side_content -> { // items aligned to the end, return end center point getViewEdgeCenter(view, outPoint, isStart = false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index dba65d1cd233..cb0a1480c233 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -54,6 +54,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.scrim.ScrimView; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java index cee8b335f380..d37ecbc42168 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java @@ -23,6 +23,8 @@ import android.view.WindowManager; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShadeWindowController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java index 50f21691b044..ebfbf54abe1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index a94c2b73d1f6..7c31366ba4f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.StatusBarIconList.Slot; + import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Icon; @@ -56,11 +58,12 @@ import javax.inject.Inject; * registered with it. */ @SysUISingleton -public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable, +public class StatusBarIconControllerImpl implements Tunable, ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode { private static final String TAG = "StatusBarIconController"; + private final StatusBarIconList mStatusBarIconList; private final ArrayList<IconManager> mIconGroups = new ArrayList<>(); private final ArraySet<String> mIconHideList = new ArraySet<>(); @@ -74,15 +77,12 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu DemoModeController demoModeController, ConfigurationController configurationController, TunerService tunerService, - DumpManager dumpManager) { - super(context.getResources().getStringArray( - com.android.internal.R.array.config_statusBarIcons)); - configurationController.addCallback(this); - + DumpManager dumpManager, + StatusBarIconList statusBarIconList) { + mStatusBarIconList = statusBarIconList; mContext = context; - loadDimens(); - + configurationController.addCallback(this); commandQueue.addCallback(this); tunerService.addTunable(this, ICON_HIDE_LIST); demoModeController.addCallback(this); @@ -101,15 +101,14 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu group.setController(this); mIconGroups.add(group); - List<Slot> allSlots = getSlots(); + List<Slot> allSlots = mStatusBarIconList.getSlots(); for (int i = 0; i < allSlots.size(); i++) { Slot slot = allSlots.get(i); List<StatusBarIconHolder> holders = slot.getHolderListInViewOrder(); boolean hidden = mIconHideList.contains(slot.getName()); for (StatusBarIconHolder holder : holders) { - int tag = holder.getTag(); - int viewIndex = getViewIndex(getSlotIndex(slot.getName()), holder.getTag()); + int viewIndex = mStatusBarIconList.getViewIndex(slot.getName(), holder.getTag()); group.onIconAdded(viewIndex, slot.getName(), hidden, holder); } } @@ -144,7 +143,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } mIconHideList.clear(); mIconHideList.addAll(StatusBarIconController.getIconHideList(mContext, newValue)); - ArrayList<Slot> currentSlots = getSlots(); + List<Slot> currentSlots = mStatusBarIconList.getSlots(); ArrayMap<Slot, List<StatusBarIconHolder>> slotsToReAdd = new ArrayMap<>(); // This is a little hacky... Peel off all of the holders on all of the slots @@ -163,17 +162,13 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu List<StatusBarIconHolder> iconsForSlot = slotsToReAdd.get(item); if (iconsForSlot == null) continue; for (StatusBarIconHolder holder : iconsForSlot) { - setIcon(getSlotIndex(item.getName()), holder); + setIcon(item.getName(), holder); } } } - private void loadDimens() { - } - - private void addSystemIcon(int index, StatusBarIconHolder holder) { - String slot = getSlotName(index); - int viewIndex = getViewIndex(index, holder.getTag()); + private void addSystemIcon(String slot, StatusBarIconHolder holder) { + int viewIndex = mStatusBarIconList.getViewIndex(slot, holder.getTag()); boolean hidden = mIconHideList.contains(slot); mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder)); @@ -182,18 +177,17 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu /** */ @Override public void setIcon(String slot, int resourceId, CharSequence contentDescription) { - int index = getSlotIndex(slot); - StatusBarIconHolder holder = getIcon(index, 0); + StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0); if (holder == null) { StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(), Icon.createWithResource( mContext, resourceId), 0, 0, contentDescription); holder = StatusBarIconHolder.fromIcon(icon); - setIcon(index, holder); + setIcon(slot, holder); } else { holder.getIcon().icon = Icon.createWithResource(mContext, resourceId); holder.getIcon().contentDescription = contentDescription; - handleSet(index, holder); + handleSet(slot, holder); } } @@ -203,21 +197,18 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu */ @Override public void setSignalIcon(String slot, WifiIconState state) { - - int index = getSlotIndex(slot); - if (state == null) { - removeIcon(index, 0); + removeIcon(slot, 0); return; } - StatusBarIconHolder holder = getIcon(index, 0); + StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0); if (holder == null) { holder = StatusBarIconHolder.fromWifiIconState(state); - setIcon(index, holder); + setIcon(slot, holder); } else { holder.setWifiState(state); - handleSet(index, holder); + handleSet(slot, holder); } } @@ -229,8 +220,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu */ @Override public void setMobileIcons(String slot, List<MobileIconState> iconStates) { - Slot mobileSlot = getSlot(slot); - int slotIndex = getSlotIndex(slot); + Slot mobileSlot = mStatusBarIconList.getSlot(slot); // Reverse the sort order to show icons with left to right([Slot1][Slot2]..). // StatusBarIconList has UI design that first items go to the right of second items. @@ -241,10 +231,10 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId); if (holder == null) { holder = StatusBarIconHolder.fromMobileIconState(state); - setIcon(slotIndex, holder); + setIcon(slot, holder); } else { holder.setMobileState(state); - handleSet(slotIndex, holder); + handleSet(slot, holder); } } } @@ -256,21 +246,19 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu */ @Override public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) { - Slot callStrengthSlot = getSlot(slot); - int callStrengthSlotIndex = getSlotIndex(slot); + Slot callStrengthSlot = mStatusBarIconList.getSlot(slot); Collections.reverse(states); for (CallIndicatorIconState state : states) { if (!state.isNoCalling) { StatusBarIconHolder holder = callStrengthSlot.getHolderForTag(state.subId); if (holder == null) { holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state); - setIcon(callStrengthSlotIndex, holder); } else { holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(), Icon.createWithResource(mContext, state.callStrengthResId), 0, 0, state.callStrengthDescription)); - setIcon(callStrengthSlotIndex, holder); } + setIcon(slot, holder); } setIconVisibility(slot, !state.isNoCalling, state.subId); } @@ -283,21 +271,19 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu */ @Override public void setNoCallingIcons(String slot, List<CallIndicatorIconState> states) { - Slot noCallingSlot = getSlot(slot); - int noCallingSlotIndex = getSlotIndex(slot); + Slot noCallingSlot = mStatusBarIconList.getSlot(slot); Collections.reverse(states); for (CallIndicatorIconState state : states) { if (state.isNoCalling) { StatusBarIconHolder holder = noCallingSlot.getHolderForTag(state.subId); if (holder == null) { holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state); - setIcon(noCallingSlotIndex, holder); } else { holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(), Icon.createWithResource(mContext, state.noCallingResId), 0, 0, state.noCallingDescription)); - setIcon(noCallingSlotIndex, holder); } + setIcon(slot, holder); } setIconVisibility(slot, state.isNoCalling, state.subId); } @@ -305,42 +291,31 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu @Override public void setExternalIcon(String slot) { - int viewIndex = getViewIndex(getSlotIndex(slot), 0); + int viewIndex = mStatusBarIconList.getViewIndex(slot, 0); int height = mContext.getResources().getDimensionPixelSize( R.dimen.status_bar_icon_drawing_size); mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height)); } //TODO: remove this (used in command queue and for 3rd party tiles?) - @Override public void setIcon(String slot, StatusBarIcon icon) { - setIcon(getSlotIndex(slot), icon); - } - - /** - * For backwards compatibility, in the event that someone gives us a slot and a status bar icon - */ - private void setIcon(int index, StatusBarIcon icon) { - String slot = getSlotName(index); if (icon == null) { removeAllIconsForSlot(slot); return; } StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon); - setIcon(index, holder); + setIcon(slot, holder); } - /** */ - @Override - public void setIcon(int index, @NonNull StatusBarIconHolder holder) { - boolean isNew = getIcon(index, holder.getTag()) == null; - super.setIcon(index, holder); + private void setIcon(String slot, @NonNull StatusBarIconHolder holder) { + boolean isNew = mStatusBarIconList.getIconHolder(slot, holder.getTag()) == null; + mStatusBarIconList.setIcon(slot, holder); if (isNew) { - addSystemIcon(index, holder); + addSystemIcon(slot, holder); } else { - handleSet(index, holder); + handleSet(slot, holder); } } @@ -351,34 +326,33 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu /** */ public void setIconVisibility(String slot, boolean visibility, int tag) { - int index = getSlotIndex(slot); - StatusBarIconHolder holder = getIcon(index, tag); + StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, tag); if (holder == null || holder.isVisible() == visibility) { return; } holder.setVisible(visibility); - handleSet(index, holder); + handleSet(slot, holder); } /** */ @Override public void setIconAccessibilityLiveRegion(String slotName, int accessibilityLiveRegion) { - Slot slot = getSlot(slotName); + Slot slot = mStatusBarIconList.getSlot(slotName); if (!slot.hasIconsInSlot()) { return; } - int slotIndex = getSlotIndex(slotName); List<StatusBarIconHolder> iconsToUpdate = slot.getHolderListInViewOrder(); for (StatusBarIconHolder holder : iconsToUpdate) { - int viewIndex = getViewIndex(slotIndex, holder.getTag()); + int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag()); mIconGroups.forEach(l -> l.mGroup.getChildAt(viewIndex) .setAccessibilityLiveRegion(accessibilityLiveRegion)); } } /** */ + @Override public void removeIcon(String slot) { removeAllIconsForSlot(slot); } @@ -386,39 +360,34 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu /** */ @Override public void removeIcon(String slot, int tag) { - removeIcon(getSlotIndex(slot), tag); + if (mStatusBarIconList.getIconHolder(slot, tag) == null) { + return; + } + int viewIndex = mStatusBarIconList.getViewIndex(slot, tag); + mStatusBarIconList.removeIcon(slot, tag); + mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); } /** */ @Override public void removeAllIconsForSlot(String slotName) { - Slot slot = getSlot(slotName); + Slot slot = mStatusBarIconList.getSlot(slotName); if (!slot.hasIconsInSlot()) { return; } - int slotIndex = getSlotIndex(slotName); List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder(); for (StatusBarIconHolder holder : iconsToRemove) { - int viewIndex = getViewIndex(slotIndex, holder.getTag()); + int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag()); slot.removeForTag(holder.getTag()); mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); } } - /** */ - @Override - public void removeIcon(int index, int tag) { - if (getIcon(index, tag) == null) { - return; - } - super.removeIcon(index, tag); - int viewIndex = getViewIndex(index, 0); - mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); - } - private void handleSet(int index, StatusBarIconHolder holder) { - int viewIndex = getViewIndex(index, holder.getTag()); + + private void handleSet(String slotName, StatusBarIconHolder holder) { + int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag()); mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder)); } @@ -438,7 +407,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } } - super.dump(pw); + mStatusBarIconList.dump(pw); } /** */ @@ -482,7 +451,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu /** */ @Override public void onDensityOrFontScaleChanged() { - loadDimens(); refreshIconGroups(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java index c876c3228eb8..8800b05fadb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java @@ -25,60 +25,72 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +/** A class holding the list of all the system icons that could be shown in the status bar. */ public class StatusBarIconList { - private ArrayList<Slot> mSlots = new ArrayList<>(); + private final ArrayList<Slot> mSlots = new ArrayList<>(); + private final List<Slot> mViewOnlySlots = Collections.unmodifiableList(mSlots); public StatusBarIconList(String[] slots) { final int N = slots.length; - for (int i=0; i < N; i++) { + for (int i = 0; i < N; i++) { mSlots.add(new Slot(slots[i], null)); } } - public int getSlotIndex(String slot) { - final int N = mSlots.size(); - for (int i=0; i<N; i++) { - Slot item = mSlots.get(i); - if (item.getName().equals(slot)) { - return i; - } - } - // Auto insert new items at the beginning. - mSlots.add(0, new Slot(slot, null)); - return 0; - } - - protected ArrayList<Slot> getSlots() { - return new ArrayList<>(mSlots); - } - - protected Slot getSlot(String name) { - return mSlots.get(getSlotIndex(name)); + /** Returns the list of current slots. */ + public List<Slot> getSlots() { + return mViewOnlySlots; } - public int size() { - return mSlots.size(); + /** + * Gets the slot with the given {@code name}, or creates a new slot if we don't already have a + * slot by that name. + * + * If a new slot is created, that slot will be inserted at the front of the list. + * + * TODO(b/237533036): Rename this to getOrCreateSlot to make it more clear that it could create + * a new slot. Other methods in this class will also create a new slot if we don't have one, + * should those be re-named too? + */ + public Slot getSlot(String name) { + return mSlots.get(findOrInsertSlot(name)); } - public void setIcon(int index, @NonNull StatusBarIconHolder holder) { - mSlots.get(index).addHolder(holder); + /** + * Sets the icon in {@code holder} to be associated with the slot with the given + * {@code slotName}. + */ + public void setIcon(String slotName, @NonNull StatusBarIconHolder holder) { + mSlots.get(findOrInsertSlot(slotName)).addHolder(holder); } - public void removeIcon(int index, int tag) { - mSlots.get(index).removeForTag(tag); + /** + * Removes the icon holder that we had associated with {@code slotName}'s slot at the given + * {@code tag}. + */ + public void removeIcon(String slotName, int tag) { + mSlots.get(findOrInsertSlot(slotName)).removeForTag(tag); } - public String getSlotName(int index) { - return mSlots.get(index).getName(); + /** + * Returns the icon holder currently associated with {@code slotName}'s slot at the given + * {@code tag}, or null if we don't have one. + */ + @Nullable + public StatusBarIconHolder getIconHolder(String slotName, int tag) { + return mSlots.get(findOrInsertSlot(slotName)).getHolderForTag(tag); } - public StatusBarIconHolder getIcon(int index, int tag) { - return mSlots.get(index).getHolderForTag(tag); - } - - public int getViewIndex(int slotIndex, int tag) { + /** + * Returns the index of the icon in {@code slotName}'s slot at the given {@code tag}. + * + * Note that a single slot can have multiple icons, and this function takes that into account. + */ + public int getViewIndex(String slotName, int tag) { + int slotIndex = findOrInsertSlot(slotName); int count = 0; for (int i = 0; i < slotIndex; i++) { Slot item = mSlots.get(i); @@ -100,6 +112,25 @@ public class StatusBarIconList { } } + private int findOrInsertSlot(String slot) { + final int N = mSlots.size(); + for (int i = 0; i < N; i++) { + Slot item = mSlots.get(i); + if (item.getName().equals(slot)) { + return i; + } + } + // Auto insert new items at the beginning. + mSlots.add(0, new Slot(slot, null)); + return 0; + } + + + /** + * A class representing one slot in the status bar system icons view. + * + * Note that one slot can have multiple icons associated with it. + */ public static class Slot { private final String mName; private StatusBarIconHolder mHolder; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 3da4fba7348c..f128a4124c94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -55,6 +55,7 @@ import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.NotificationMediaManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 72db884b4cac..451612ad4bb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -52,6 +52,7 @@ import com.android.systemui.EventLogTags; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index aebf0b5325ce..4bbd69b91a0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -39,6 +39,8 @@ import com.android.systemui.InitController; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 4e9090080c99..75dac1a093dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -136,8 +136,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable { * Notify that the status bar panel gets expanded or collapsed. * * @param isExpanded True to notify expanded, false to notify collapsed. + * TODO(b/237811427) replace with a listener */ - void setPanelExpanded(boolean isExpanded) { + public void setPanelExpanded(boolean isExpanded) { if (isExpanded != mIsStatusBarExpanded) { mIsStatusBarExpanded = isExpanded; if (isExpanded) { @@ -153,7 +154,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { * any existing display cutouts (notch) * @return the heads up notification touch area */ - Region calculateTouchableRegion() { + public Region calculateTouchableRegion() { // Update touchable region for HeadsUp notifications final Region headsUpTouchableRegion = mHeadsUpManager.getTouchableRegion(); if (headsUpTouchableRegion != null) { @@ -222,7 +223,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { } } - void updateRegionForNotch(Region touchableRegion) { + public void updateRegionForNotch(Region touchableRegion) { WindowInsets windowInsets = mNotificationShadeWindowView.getRootWindowInsets(); if (windowInsets == null) { Log.w(TAG, "StatusBarWindowView is not attached."); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index c5e5297ae6ba..84b279760f36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -22,6 +22,9 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.keyguard.LockIconViewController; import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.core.StatusBarInitializer; @@ -33,9 +36,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks; import com.android.systemui.statusbar.phone.CentralSurfacesImpl; import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowView; -import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController; import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule; import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index ea4ecd5f5ab4..41df8e3cbb31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -34,6 +34,10 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.privacy.OngoingPrivacyChip; +import com.android.systemui.shade.NotificationPanelView; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.shade.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; @@ -44,10 +48,6 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.NotificationPanelView; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowView; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 597c949168d4..39bf92a4dcbb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -48,6 +48,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger.DisableState; import com.android.systemui.statusbar.OperatorNameView; @@ -59,7 +60,6 @@ import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -102,7 +102,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final KeyguardStateController mKeyguardStateController; private final NotificationPanelViewController mNotificationPanelViewController; private final NetworkController mNetworkController; - private LinearLayout mSystemIconArea; + private LinearLayout mEndSideContent; private View mClockView; private View mOngoingCallChip; private View mNotificationIconAreaInner; @@ -232,16 +232,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mDarkIconManager.setShouldLog(true); updateBlockedIcons(); mStatusBarIconController.addIconGroup(mDarkIconManager); - mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area); + mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content); mClockView = mStatusBar.findViewById(R.id.clock); mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip); - showSystemIconArea(false); + showEndSideContent(false); showClock(false); initEmergencyCryptkeeperText(); initOperatorName(); initNotificationIconArea(); mSystemEventAnimator = - new StatusBarSystemEventAnimator(mSystemIconArea, getResources()); + new StatusBarSystemEventAnimator(mEndSideContent, getResources()); mCarrierConfigTracker.addCallback(mCarrierConfigCallback); mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener); } @@ -370,10 +370,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mDisabled2 = state2; if ((diff1 & DISABLE_SYSTEM_INFO) != 0 || ((diff2 & DISABLE2_SYSTEM_ICONS) != 0)) { if ((state1 & DISABLE_SYSTEM_INFO) != 0 || ((state2 & DISABLE2_SYSTEM_ICONS) != 0)) { - hideSystemIconArea(animate); + hideEndSideContent(animate); hideOperatorName(animate); } else { - showSystemIconArea(animate); + showEndSideContent(animate); showOperatorName(animate); } } @@ -474,15 +474,15 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer(); } - private void hideSystemIconArea(boolean animate) { - animateHide(mSystemIconArea, animate); + private void hideEndSideContent(boolean animate) { + animateHide(mEndSideContent, animate); } - private void showSystemIconArea(boolean animate) { + private void showEndSideContent(boolean animate) { // Only show the system icon area if we are not currently animating int state = mAnimationScheduler.getAnimationState(); if (state == IDLE || state == SHOWING_PERSISTENT_DOT) { - animateShow(mSystemIconArea, animate); + animateShow(mEndSideContent, animate); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index d5f5362eaf3c..7252dfb76c14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -21,8 +21,8 @@ import android.view.View; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.HeadsUpStatusBarView; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index a29ba916eb41..337ffdf46560 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -28,9 +28,9 @@ import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.ToggleSlider; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import java.util.Objects; import java.util.function.Consumer; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 2fb16ee9b3b9..bdac88837969 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -424,11 +424,6 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override - public void onFaceUnlockStateChanged(boolean running, int userId) { - update(false /* updateAlways */); - } - - @Override public void onStrongAuthStateChanged(int userId) { update(false /* updateAlways */); } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index 7350b37e4b66..13ac39c77c93 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -18,7 +18,7 @@ package com.android.systemui.unfold import com.android.keyguard.KeyguardUnfoldTransition import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.phone.NotificationPanelUnfoldAnimationController +import com.android.systemui.shade.NotificationPanelUnfoldAnimationController import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index bca2a24ff12d..708a8ab4fcf6 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -34,7 +34,6 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.content.pm.UserInfo; -import android.content.res.Configuration; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -72,7 +71,6 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; -import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.bubbles.Bubble; @@ -126,7 +124,6 @@ public class BubblesManager implements Dumpable { NotificationShadeWindowController notificationShadeWindowController, KeyguardStateController keyguardStateController, ShadeController shadeController, - ConfigurationController configurationController, @Nullable IStatusBarService statusBarService, INotificationManager notificationManager, NotificationVisibilityProvider visibilityProvider, @@ -145,7 +142,6 @@ public class BubblesManager implements Dumpable { notificationShadeWindowController, keyguardStateController, shadeController, - configurationController, statusBarService, notificationManager, visibilityProvider, @@ -169,7 +165,6 @@ public class BubblesManager implements Dumpable { NotificationShadeWindowController notificationShadeWindowController, KeyguardStateController keyguardStateController, ShadeController shadeController, - ConfigurationController configurationController, @Nullable IStatusBarService statusBarService, INotificationManager notificationManager, NotificationVisibilityProvider visibilityProvider, @@ -213,23 +208,6 @@ public class BubblesManager implements Dumpable { } }); - configurationController.addCallback(new ConfigurationController.ConfigurationListener() { - @Override - public void onConfigChanged(Configuration newConfig) { - mBubbles.onConfigChanged(newConfig); - } - - @Override - public void onUiModeChanged() { - mBubbles.updateForThemeChanges(); - } - - @Override - public void onThemeChanged() { - mBubbles.updateForThemeChanges(); - } - }); - zenModeController.addCallback(new ZenModeController.Callback() { @Override public void onZenChanged(int zen) { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index a6db2aa48a88..83b0022b9f53 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -47,7 +47,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; -import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -56,9 +55,6 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; import com.android.wm.shell.ShellCommandHandler; -import com.android.wm.shell.compatui.CompatUI; -import com.android.wm.shell.draganddrop.DragAndDrop; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.nano.WmShellTraceProto; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEventCallback; @@ -67,6 +63,7 @@ import com.android.wm.shell.onehanded.OneHandedUiEventLogger; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.protolog.ShellProtoLogImpl; import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.sysui.ShellInterface; import java.io.PrintWriter; import java.util.Arrays; @@ -106,19 +103,16 @@ public final class WMShell extends CoreStartable | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; // Shell interfaces + private final ShellInterface mShell; private final Optional<Pip> mPipOptional; private final Optional<SplitScreen> mSplitScreenOptional; private final Optional<OneHanded> mOneHandedOptional; - private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional; private final Optional<ShellCommandHandler> mShellCommandHandler; - private final Optional<CompatUI> mCompatUIOptional; - private final Optional<DragAndDrop> mDragAndDropOptional; private final CommandQueue mCommandQueue; private final ConfigurationController mConfigurationController; private final KeyguardStateController mKeyguardStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final NavigationModeController mNavigationModeController; private final ScreenLifecycle mScreenLifecycle; private final SysUiState mSysUiState; private final WakefulnessLifecycle mWakefulnessLifecycle; @@ -127,26 +121,20 @@ public final class WMShell extends CoreStartable private final Executor mSysUiMainExecutor; private boolean mIsSysUiStateValid; - private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback; - private KeyguardUpdateMonitorCallback mPipKeyguardCallback; private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback; - private KeyguardStateController.Callback mCompatUIKeyguardCallback; private WakefulnessLifecycle.Observer mWakefulnessObserver; @Inject public WMShell(Context context, + ShellInterface shell, Optional<Pip> pipOptional, Optional<SplitScreen> splitScreenOptional, Optional<OneHanded> oneHandedOptional, - Optional<HideDisplayCutout> hideDisplayCutoutOptional, Optional<ShellCommandHandler> shellCommandHandler, - Optional<CompatUI> sizeCompatUIOptional, - Optional<DragAndDrop> dragAndDropOptional, CommandQueue commandQueue, ConfigurationController configurationController, KeyguardStateController keyguardStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - NavigationModeController navigationModeController, ScreenLifecycle screenLifecycle, SysUiState sysUiState, ProtoTracer protoTracer, @@ -154,28 +142,50 @@ public final class WMShell extends CoreStartable UserInfoController userInfoController, @Main Executor sysUiMainExecutor) { super(context); + mShell = shell; mCommandQueue = commandQueue; mConfigurationController = configurationController; mKeyguardStateController = keyguardStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mNavigationModeController = navigationModeController; mScreenLifecycle = screenLifecycle; mSysUiState = sysUiState; mPipOptional = pipOptional; mSplitScreenOptional = splitScreenOptional; mOneHandedOptional = oneHandedOptional; - mHideDisplayCutoutOptional = hideDisplayCutoutOptional; mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; mShellCommandHandler = shellCommandHandler; - mCompatUIOptional = sizeCompatUIOptional; - mDragAndDropOptional = dragAndDropOptional; mUserInfoController = userInfoController; mSysUiMainExecutor = sysUiMainExecutor; } @Override public void start() { + // Notify with the initial configuration and subscribe for new config changes + mShell.onConfigurationChanged(mContext.getResources().getConfiguration()); + mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() { + @Override + public void onConfigChanged(Configuration newConfig) { + mShell.onConfigurationChanged(newConfig); + } + }); + + // Subscribe to keyguard changes + mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { + @Override + public void onKeyguardShowingChanged() { + mShell.onKeyguardVisibilityChanged(mKeyguardStateController.isShowing(), + mKeyguardStateController.isOccluded(), + mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind()); + } + }); + mKeyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() { + @Override + public void onKeyguardDismissAnimationFinished() { + mShell.onKeyguardDismissAnimationFinished(); + } + }); + // TODO: Consider piping config change and other common calls to a shell component to // delegate internally mProtoTracer.add(this); @@ -183,9 +193,6 @@ public final class WMShell extends CoreStartable mPipOptional.ifPresent(this::initPip); mSplitScreenOptional.ifPresent(this::initSplitScreen); mOneHandedOptional.ifPresent(this::initOneHanded); - mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout); - mCompatUIOptional.ifPresent(this::initCompatUi); - mDragAndDropOptional.ifPresent(this::initDragAndDrop); } @VisibleForTesting @@ -197,42 +204,11 @@ public final class WMShell extends CoreStartable } }); - mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onKeyguardVisibilityChanged(boolean showing) { - pip.onKeyguardVisibilityChanged(showing, - mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind()); - } - - @Override - public void onKeyguardDismissAnimationFinished() { - pip.onKeyguardDismissAnimationFinished(); - } - }; - mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback); - mSysUiState.addCallback(sysUiStateFlag -> { mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0; pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag); }); - mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() { - @Override - public void onConfigChanged(Configuration newConfig) { - pip.onConfigurationChanged(newConfig); - } - - @Override - public void onDensityOrFontScaleChanged() { - pip.onDensityOrFontScaleChanged(); - } - - @Override - public void onThemeChanged() { - pip.onOverlayChanged(); - } - }); - // The media session listener needs to be re-registered when switching users mUserInfoController.addCallback((String name, Drawable picture, String userAccount) -> pip.registerSessionListenerForCurrentUser()); @@ -240,14 +216,6 @@ public final class WMShell extends CoreStartable @VisibleForTesting void initSplitScreen(SplitScreen splitScreen) { - mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onKeyguardVisibilityChanged(boolean showing) { - splitScreen.onKeyguardVisibilityChanged(showing); - } - }; - mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback); - mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() { @Override public void onFinishedWakingUp() { @@ -293,14 +261,9 @@ public final class WMShell extends CoreStartable } }); + // TODO: Either move into ShellInterface or register a receiver on the Shell side directly mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onKeyguardVisibilityChanged(boolean showing) { - oneHanded.onKeyguardVisibilityChanged(showing); - oneHanded.stopOneHanded(); - } - - @Override public void onUserSwitchComplete(int userId) { oneHanded.onUserSwitch(userId); } @@ -348,48 +311,6 @@ public final class WMShell extends CoreStartable } } }); - - mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() { - @Override - public void onConfigChanged(Configuration newConfig) { - oneHanded.onConfigChanged(newConfig); - } - }); - } - - @VisibleForTesting - void initHideDisplayCutout(HideDisplayCutout hideDisplayCutout) { - mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() { - @Override - public void onConfigChanged(Configuration newConfig) { - hideDisplayCutout.onConfigurationChanged(newConfig); - } - }); - } - - @VisibleForTesting - void initCompatUi(CompatUI sizeCompatUI) { - mCompatUIKeyguardCallback = new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - sizeCompatUI.onKeyguardShowingChanged(mKeyguardStateController.isShowing()); - } - }; - mKeyguardStateController.addCallback(mCompatUIKeyguardCallback); - } - - void initDragAndDrop(DragAndDrop dragAndDrop) { - mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() { - @Override - public void onConfigChanged(Configuration newConfig) { - dragAndDrop.onConfigChanged(newConfig); - } - - @Override - public void onThemeChanged() { - dragAndDrop.onThemeChanged(); - } - }); } @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2dc066c8a9db..84903d17852f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -1109,8 +1109,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN face unlock is not running b/c status bar state changes don't cause biometric // listening state to update - assertThat(mKeyguardUpdateMonitor.isFaceUnlockRunning( - KeyguardUpdateMonitor.getCurrentUser())).isEqualTo(false); + assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(false); // WHEN biometric listening state is updated mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java index 7e9f84c1ef8c..bebd8712a750 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java @@ -191,6 +191,16 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase { } @Test + public void hideMenuViewWhenStartingAnimation_animatorNotRunning() { + mMenuView.show(); + + mMenuView.mDragAnimator.start(); + mMenuView.hide(); + + assertThat(mMenuView.mDragAnimator.isRunning()).isFalse(); + } + + @Test public void onTargetsChanged_singleTarget_expectedRadii() { final Position alignRightPosition = new Position(1.0f, 0.0f); final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index 3c7ea4fe6f35..c31fd828c730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -37,9 +37,14 @@ import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import java.io.File +import java.util.Optional +import java.util.function.Consumer import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -50,20 +55,20 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyInt -import org.mockito.Mockito.`when` import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.Optional -import java.util.function.Consumer @SmallTest @RunWith(AndroidTestingRunner::class) @@ -85,6 +90,8 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var listingController: ControlsListingController @Mock(stubOnly = true) private lateinit var userTracker: UserTracker + @Mock + private lateinit var userFileManager: UserFileManager @Captor private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo> @@ -153,6 +160,9 @@ class ControlsControllerImplTest : SysuiTestCase() { canceller = DidRunRunnable() `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller) + `when`(userFileManager.getFile(anyString(), anyInt())).thenReturn(mock(File::class.java)) + `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) + .thenReturn(context.getSharedPreferences("test", Context.MODE_PRIVATE)) controller = ControlsControllerImpl( wrapper, @@ -161,6 +171,7 @@ class ControlsControllerImplTest : SysuiTestCase() { bindingController, listingController, broadcastDispatcher, + userFileManager, Optional.of(persistenceWrapper), mock(DumpManager::class.java), userTracker @@ -217,6 +228,7 @@ class ControlsControllerImplTest : SysuiTestCase() { bindingController, listingController, broadcastDispatcher, + userFileManager, Optional.of(persistenceWrapper), mock(DumpManager::class.java), userTracker @@ -911,6 +923,14 @@ class ControlsControllerImplTest : SysuiTestCase() { assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty()) } + + @Test + fun testUserStructure() { + val userStructure = UserStructure(context, context.user, userFileManager) + verify(userFileManager, times(2)) + .getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, context.user.identifier) + assertThat(userStructure.file).isNotNull() + } } private class DidRunRunnable() : Runnable { diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java index e5a75e231f8d..49cdfa72f344 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java @@ -86,7 +86,7 @@ public class DozeUiTest extends SysuiTestCase { mHandler = mHandlerThread.getThreadHandler(); mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, - mDozeParameters, mKeyguardUpdateMonitor, mStatusBarStateController, mDozeLog); + mDozeParameters, mStatusBarStateController, mDozeLog); mDozeUi.setDozeMachine(mMachine); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index d334694805fe..60e5a9423c61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -184,25 +184,36 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void testOnViewAttachedShowsMicCameraIconWhenDisabled() { + public void testOnViewAttachedShowsMicIconWhenDisabled() { when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE)) .thenReturn(true); when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA)) - .thenReturn(true); + .thenReturn(false); mController.onViewAttached(); verify(mView).showIcon( - DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true, null); + DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED, true, null); } @Test - public void testOnViewAttachedHidesMicCameraIconWhenEnabled() { + public void testOnViewAttachedShowsCameraIconWhenDisabled() { when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE)) .thenReturn(false); when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA)) - .thenReturn(false); + .thenReturn(true); + mController.onViewAttached(); + verify(mView).showIcon( + DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED, true, null); + } + + @Test + public void testOnViewAttachedShowsMicCameraIconWhenDisabled() { + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE)) + .thenReturn(true); + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA)) + .thenReturn(true); mController.onViewAttached(); verify(mView).showIcon( - DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, false, null); + DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true, null); } @Test @@ -386,24 +397,6 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void testMicCameraIconHiddenWhenSensorsNotBlocked() { - when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE)) - .thenReturn(true).thenReturn(false); - when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA)) - .thenReturn(true).thenReturn(false); - mController.onViewAttached(); - - final ArgumentCaptor<IndividualSensorPrivacyController.Callback> callbackCapture = - ArgumentCaptor.forClass(IndividualSensorPrivacyController.Callback.class); - verify(mSensorPrivacyController).addCallback(callbackCapture.capture()); - callbackCapture.getValue().onSensorBlockedChanged( - SensorPrivacyManager.Sensors.MICROPHONE, false); - - verify(mView).showIcon( - DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, false, null); - } - - @Test public void testPriorityModeIconShownWhenZenModeEnabled() { mController.onViewAttached(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/AirQualityColorPickerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/AirQualityColorPickerTest.java deleted file mode 100644 index 33be5dcfe0a0..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/AirQualityColorPickerTest.java +++ /dev/null @@ -1,107 +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.dreams.complication; - -import static com.google.common.truth.Truth.assertThat; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class AirQualityColorPickerTest extends SysuiTestCase { - private static final int DEFAULT_COLOR = 0; - private static final int MOCK_COLOR_1 = 1; - private static final int MOCK_COLOR_2 = 2; - private static final int MOCK_COLOR_3 = 3; - private static final int MOCK_COLOR_4 = 4; - private static final int MOCK_COLOR_5 = 5; - - private static final int[] MOCK_THRESHOLDS = {-1, 100, 200, 201, 500}; - private static final int[] MOCK_COLORS = - {MOCK_COLOR_1, MOCK_COLOR_2, MOCK_COLOR_3, MOCK_COLOR_4, MOCK_COLOR_5}; - private static final int[] EMPTY_ARRAY = {}; - - @Test - public void testEmptyThresholds() { - final AirQualityColorPicker colorPicker = new AirQualityColorPicker( - EMPTY_ARRAY, - MOCK_COLORS, - DEFAULT_COLOR); - assertThat(colorPicker.getColorForValue("110 AQI")).isEqualTo(DEFAULT_COLOR); - } - - @Test - public void testEmptyColors() { - final AirQualityColorPicker colorPicker = new AirQualityColorPicker( - MOCK_THRESHOLDS, - EMPTY_ARRAY, - DEFAULT_COLOR); - assertThat(colorPicker.getColorForValue("110 AQI")).isEqualTo(DEFAULT_COLOR); - } - - @Test - public void testEmptyAqiString() { - final AirQualityColorPicker colorPicker = new AirQualityColorPicker( - MOCK_THRESHOLDS, - MOCK_COLORS, - DEFAULT_COLOR); - assertThat(colorPicker.getColorForValue("")).isEqualTo(DEFAULT_COLOR); - } - - @Test - public void testInvalidAqiString() { - final AirQualityColorPicker colorPicker = new AirQualityColorPicker( - MOCK_THRESHOLDS, - MOCK_COLORS, - DEFAULT_COLOR); - assertThat(colorPicker.getColorForValue("invalid")).isEqualTo(DEFAULT_COLOR); - } - - @Test - public void testZeroAirQuality() { - final AirQualityColorPicker colorPicker = new AirQualityColorPicker( - MOCK_THRESHOLDS, - MOCK_COLORS, - DEFAULT_COLOR); - assertThat(colorPicker.getColorForValue("0 AQI")).isEqualTo(MOCK_COLOR_1); - } - - @Test - public void testVeryLargeAirQuality() { - final AirQualityColorPicker colorPicker = new AirQualityColorPicker( - MOCK_THRESHOLDS, - MOCK_COLORS, - DEFAULT_COLOR); - assertThat(colorPicker.getColorForValue("100000 AQI")).isEqualTo(MOCK_COLOR_5); - } - - @Test - public void testAirQuality200() { - final AirQualityColorPicker colorPicker = new AirQualityColorPicker( - MOCK_THRESHOLDS, - MOCK_COLORS, - DEFAULT_COLOR); - assertThat(colorPicker.getColorForValue("200 AQI")).isEqualTo(MOCK_COLOR_2); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamAirQualityComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamAirQualityComplicationTest.java deleted file mode 100644 index b8a7059e4bb7..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamAirQualityComplicationTest.java +++ /dev/null @@ -1,91 +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.dreams.complication; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.testing.AndroidTestingRunner; -import android.widget.TextView; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.dreams.complication.DreamAirQualityComplication.DreamAirQualityViewController; -import com.android.systemui.dreams.smartspace.DreamSmartspaceController; -import com.android.systemui.plugins.ActivityStarter; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class DreamAirQualityComplicationTest extends SysuiTestCase { - private static final String TRAMPOLINE_COMPONENT = "TestComponent"; - - @Mock - private DreamSmartspaceController mDreamSmartspaceController; - - @Mock - private DreamOverlayStateController mDreamOverlayStateController; - - @Mock - private DreamAirQualityComplication mComplication; - - @Mock - private AirQualityColorPicker mColorPicker; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - /** - * Ensures {@link DreamAirQualityComplication} is registered. - */ - @Test - public void testComplicationRegistered() { - final DreamAirQualityComplication.Registrant registrant = - new DreamAirQualityComplication.Registrant( - mContext, - mDreamOverlayStateController, - mComplication); - registrant.start(); - verify(mDreamOverlayStateController).addComplication(eq(mComplication)); - } - - @Test - public void testGetUnfilteredTargets() { - final DreamAirQualityViewController controller = - new DreamAirQualityViewController( - mock(TextView.class), - mDreamSmartspaceController, - TRAMPOLINE_COMPONENT, - mock(ActivityStarter.class), - mColorPicker); - controller.onViewAttached(); - verify(mDreamSmartspaceController).addUnfilteredListener(any()); - controller.onViewDetached(); - verify(mDreamSmartspaceController).removeUnfilteredListener(any()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java deleted file mode 100644 index a23c4b50082f..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java +++ /dev/null @@ -1,89 +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.dreams.complication; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.content.res.Resources; -import android.testing.AndroidTestingRunner; -import android.widget.TextView; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.dreams.smartspace.DreamSmartspaceController; -import com.android.systemui.plugins.ActivityStarter; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class DreamWeatherComplicationTest extends SysuiTestCase { - private static final String TRAMPOLINE_COMPONENT = "TestComponent"; - - @SuppressWarnings("HidingField") - @Mock - private Context mContext; - - @Mock - private DreamSmartspaceController mDreamSmartspaceController; - - @Mock - private DreamOverlayStateController mDreamOverlayStateController; - - @Mock - private DreamWeatherComplication mComplication; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - /** - * Ensures {@link DreamWeatherComplication} is registered. - */ - @Test - public void testComplicationRegistered() { - final DreamWeatherComplication.Registrant registrant = - new DreamWeatherComplication.Registrant( - mContext, - mDreamOverlayStateController, - mComplication); - registrant.start(); - verify(mDreamOverlayStateController).addComplication(eq(mComplication)); - } - - @Test - public void testGetUnfilteredTargets() { - final DreamWeatherComplication.DreamWeatherViewController controller = - new DreamWeatherComplication.DreamWeatherViewController(mock( - TextView.class), TRAMPOLINE_COMPONENT, mock(ActivityStarter.class), - mDreamSmartspaceController, mock(Resources.class)); - controller.onViewAttached(); - verify(mDreamSmartspaceController).addUnfilteredListener(any()); - controller.onViewDetached(); - verify(mDreamSmartspaceController).removeUnfilteredListener(any()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index c13c30baed0a..178502269e73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -561,6 +561,19 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test + fun bindAlbumView_artUsesResource() { + val albumArt = Icon.createWithResource(context, R.drawable.ic_android) + val state = mediaData.copy(artwork = albumArt) + + player.attachPlayer(viewHolder) + player.bindPlayer(state, PACKAGE) + bgExecutor.runAllReady() + mainExecutor.runAllReady() + + verify(albumView).setImageDrawable(any(Drawable::class.java)) + } + + @Test fun bindAlbumView_setAfterExecutors() { val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) val canvas = Canvas(bmp) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 226182961934..568e0cb22f18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -78,7 +78,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices); when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false); - when(mMediaOutputController.isZeroMode()).thenReturn(false); when(mMediaOutputController.isTransferring()).thenReturn(false); when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat); when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat); @@ -98,28 +97,12 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test - public void getItemCount_nonZeroMode_isDeviceSize() { - assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size()); - } - - @Test - public void getItemCount_zeroMode_containExtraOneForPairNew() { - when(mMediaOutputController.isZeroMode()).thenReturn(true); - + public void getItemCount_containExtraOneForPairNew() { assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size() + 1); } @Test - public void getItemCount_withDynamicGroup_containExtraOneForGroup() { - when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices); - when(mMediaOutputController.isZeroMode()).thenReturn(false); - - assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size()); - } - - @Test - public void onBindViewHolder_zeroMode_bindPairNew_verifyView() { - when(mMediaOutputController.isZeroMode()).thenReturn(true); + public void onBindViewHolder_bindPairNew_verifyView() { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); @@ -133,7 +116,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindGroup_withSessionName_verifyView() { when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices); - when(mMediaOutputController.isZeroMode()).thenReturn(false); when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME); mMediaOutputAdapter.getItemCount(); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -148,7 +130,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onBindViewHolder_bindGroup_noSessionName_verifyView() { when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices); - when(mMediaOutputController.isZeroMode()).thenReturn(false); when(mMediaOutputController.getSessionName()).thenReturn(null); mMediaOutputAdapter.getItemCount(); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); @@ -257,7 +238,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { @Test public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() { - when(mMediaOutputController.isZeroMode()).thenReturn(true); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); mViewHolder.mContainerLayout.performClick(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 2bf5f0fcbfcb..751c8951859c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -383,62 +383,6 @@ public class MediaOutputControllerTest extends SysuiTestCase { } @Test - public void isZeroMode_onlyFromPhoneOutput_returnTrue() { - // Multiple available devices - assertThat(mMediaOutputController.isZeroMode()).isFalse(); - when(mMediaDevice1.getDeviceType()).thenReturn( - MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE); - mMediaDevices.clear(); - mMediaDevices.add(mMediaDevice1); - mMediaOutputController.start(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); - - assertThat(mMediaOutputController.isZeroMode()).isTrue(); - - when(mMediaDevice1.getDeviceType()).thenReturn( - MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE); - - assertThat(mMediaOutputController.isZeroMode()).isTrue(); - - when(mMediaDevice1.getDeviceType()).thenReturn( - MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE); - - assertThat(mMediaOutputController.isZeroMode()).isTrue(); - } - - @Test - public void isZeroMode_notFromPhoneOutput_returnFalse() { - when(mMediaDevice1.getDeviceType()).thenReturn( - MediaDevice.MediaDeviceType.TYPE_UNKNOWN); - mMediaDevices.clear(); - mMediaDevices.add(mMediaDevice1); - mMediaOutputController.start(mCb); - mMediaOutputController.onDeviceListUpdate(mMediaDevices); - - assertThat(mMediaOutputController.isZeroMode()).isFalse(); - - when(mMediaDevice1.getDeviceType()).thenReturn( - MediaDevice.MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE); - - assertThat(mMediaOutputController.isZeroMode()).isFalse(); - - when(mMediaDevice1.getDeviceType()).thenReturn( - MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); - - assertThat(mMediaOutputController.isZeroMode()).isFalse(); - - when(mMediaDevice1.getDeviceType()).thenReturn( - MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); - - assertThat(mMediaOutputController.isZeroMode()).isFalse(); - - when(mMediaDevice1.getDeviceType()).thenReturn( - MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE); - - assertThat(mMediaOutputController.isZeroMode()).isFalse(); - } - - @Test public void getGroupMediaDevices_differentDeviceOrder_showingSameOrder() { final MediaDevice selectedMediaDevice1 = mock(MediaDevice.class); final MediaDevice selectedMediaDevice2 = mock(MediaDevice.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 1cfa3b2a08ff..8fa5c9324416 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -89,6 +89,7 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; +import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -97,7 +98,6 @@ import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LightBarTransitionsController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java index 99f21ad4d508..c8ebd1240149 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java @@ -48,6 +48,8 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.wmshell.BubblesManager; import com.android.wm.shell.bubbles.Bubble; @@ -106,6 +108,9 @@ public class LaunchConversationActivityTest extends SysuiTestCase { private Intent mIntent; + private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); + private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -114,7 +119,8 @@ public class LaunchConversationActivityTest extends SysuiTestCase { mNotifCollection, Optional.of(mBubblesManager), mUserManager, - mCommandQueue + mCommandQueue, + mBgExecutor ); verify(mCommandQueue, times(1)).addCallback(mCallbacksCaptor.capture()); @@ -193,6 +199,7 @@ public class LaunchConversationActivityTest extends SysuiTestCase { // Ensure callback removed verify(mCommandQueue).removeCallback(any()); // Clear the notification for bubbles. + FakeExecutor.exhaustExecutors(mBgExecutor); verify(mIStatusBarService, times(1)).onNotificationClear(any(), anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture()); // Do not select the bubble. diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index 91cafead596c..b05d9a31b475 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -16,18 +16,21 @@ package com.android.systemui.screenrecord; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.Notification; import android.app.NotificationManager; import android.content.Intent; +import android.os.Handler; import android.os.RemoteException; import android.testing.AndroidTestingRunner; @@ -66,6 +69,8 @@ public class RecordingServiceTest extends SysuiTestCase { @Mock private Executor mExecutor; @Mock + private Handler mHandler; + @Mock private UserContextProvider mUserContextTracker; private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() { public void executeWhenUnlocked(ActivityStarter.OnDismissAction action, @@ -79,8 +84,8 @@ public class RecordingServiceTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger, - mNotificationManager, mUserContextTracker, mKeyguardDismissUtil)); + mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler, + mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil)); // Return actual context info doReturn(mContext).when(mRecordingService).getApplicationContext(); @@ -143,4 +148,54 @@ public class RecordingServiceTest extends SysuiTestCase { // Then the state is set to not recording verify(mController).updateState(false); } + + @Test + public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException { + doReturn(true).when(mController).isRecording(); + + mRecordingService.onStopped(); + + verify(mScreenMediaRecorder).end(); + } + + @Test + public void testOnSystemRequestedStop_recordingInProgress_updatesState() { + doReturn(true).when(mController).isRecording(); + + mRecordingService.onStopped(); + + verify(mController).updateState(false); + } + + @Test + public void testOnSystemRequestedStop_recordingIsNotInProgress_doesNotEndRecording() + throws IOException { + doReturn(false).when(mController).isRecording(); + + mRecordingService.onStopped(); + + verify(mScreenMediaRecorder, never()).end(); + } + + @Test + public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording() + throws IOException { + doReturn(true).when(mController).isRecording(); + doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(); + + mRecordingService.onStopped(); + + verify(mScreenMediaRecorder).release(); + } + + @Test + public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording() + throws IOException { + doReturn(true).when(mController).isRecording(); + doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end(); + + assertThrows(Throwable.class, () -> mRecordingService.onStopped()); + + verify(mScreenMediaRecorder).release(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 98cd0804bfd2..5abcff3c56f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -106,6 +106,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -131,8 +132,28 @@ import com.android.systemui.statusbar.notification.stack.NotificationRoundnessMa import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.KeyguardStatusBarView; +import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; +import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; +import com.android.systemui.statusbar.phone.NotificationIconAreaController; +import com.android.systemui.statusbar.phone.PanelViewController; +import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; +import com.android.systemui.statusbar.phone.TapAgainViewController; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; -import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -563,7 +584,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(View.AccessibilityDelegate.class); verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture()); mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue(); - mNotificationPanelViewController.mStatusBarStateController + mNotificationPanelViewController.getStatusBarStateController() .addCallback(mNotificationPanelViewController.mStatusBarStateListener); mNotificationPanelViewController .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); @@ -998,7 +1019,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mNotificationPanelViewController.flingToHeight( 0f, true, - mNotificationPanelViewController.mExpandedHeight, + mNotificationPanelViewController.getExpandedHeight(), 1f, false); // Verify that the NSSL is notified that the panel is *not* flinging. @@ -1192,14 +1213,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mPowerManager.isPowerSaveMode()).thenReturn(false); when(mAmbientState.getDozeAmount()).thenReturn(0f); mNotificationPanelViewController.startUnlockHintAnimation(); - assertThat(mNotificationPanelViewController.mHintAnimationRunning).isTrue(); + assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isTrue(); } @Test public void testUnlockHintAnimation_doesNotRun_inPowerSaveMode() { when(mPowerManager.isPowerSaveMode()).thenReturn(true); mNotificationPanelViewController.startUnlockHintAnimation(); - assertThat(mNotificationPanelViewController.mHintAnimationRunning).isFalse(); + assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse(); } @Test @@ -1207,7 +1228,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mPowerManager.isPowerSaveMode()).thenReturn(false); when(mAmbientState.getDozeAmount()).thenReturn(0.5f); mNotificationPanelViewController.startUnlockHintAnimation(); - assertThat(mNotificationPanelViewController.mHintAnimationRunning).isFalse(); + assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt index de40b7fd0a13..0c6a6a98052f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone +package com.android.systemui.shade import android.testing.AndroidTestingRunner import android.testing.TestableLooper diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 0e42d9a576f7..1dfd7c26ecbb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; @@ -55,6 +55,11 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl; +import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 7982bc0c101c..471918cfa695 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone +package com.android.systemui.shade import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -32,7 +32,10 @@ import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.statusbar.phone.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.tuner.TunerService diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index 1d86fb13a1f6..665d849e379e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shade; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -47,6 +47,9 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.tuner.TunerService; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt index 304a274576b7..baaa447d53a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone.shade.transition +package com.android.systemui.shade.transition import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt index 8b7e04bbab1b..b6f8326394fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone.shade.transition +package com.android.systemui.shade.transition import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest @@ -6,8 +6,10 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.qs.QS +import com.android.systemui.shade.NotificationPanelViewController +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.statusbar.phone.NotificationPanelViewController import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING @@ -19,6 +21,7 @@ import org.mockito.Mock import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -32,6 +35,7 @@ class ShadeTransitionControllerTest : SysuiTestCase() { @Mock private lateinit var splitShadeOverScroller: SplitShadeOverScroller @Mock private lateinit var scrimShadeTransitionController: ScrimShadeTransitionController @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController private lateinit var controller: ShadeTransitionController @@ -50,7 +54,9 @@ class ShadeTransitionControllerTest : SysuiTestCase() { context, splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller }, noOpOverScroller, - scrimShadeTransitionController) + scrimShadeTransitionController, + statusBarStateController, + ) // Resetting as they are notified upon initialization. reset(noOpOverScroller, splitShadeOverScroller) @@ -80,6 +86,45 @@ class ShadeTransitionControllerTest : SysuiTestCase() { } @Test + fun onPanelStateChanged_inSplitShade_onKeyguard_forwardsToNoOpOverScroller() { + initLateProperties() + enableSplitShade() + setOnKeyguard() + + startPanelExpansion() + + verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING) + verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(splitShadeOverScroller) + } + + @Test + fun onPanelStateChanged_inSplitShade_onLockedShade_forwardsToNoOpOverScroller() { + initLateProperties() + enableSplitShade() + setOnLockedShade() + + startPanelExpansion() + + verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING) + verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(splitShadeOverScroller) + } + + @Test + fun onPanelExpansionChanged_inSplitShade_onUnlockedShade_forwardsToSplitShadeOverScroller() { + initLateProperties() + enableSplitShade() + setOnUnlockedShade() + + startPanelExpansion() + + verify(splitShadeOverScroller).onPanelStateChanged(STATE_OPENING) + verify(splitShadeOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT) + verifyZeroInteractions(noOpOverScroller) + } + + @Test fun onPanelStateChanged_notInSplitShade_forwardsToNoOpOverScroller() { initLateProperties() disableSplitShade() @@ -129,6 +174,23 @@ class ShadeTransitionControllerTest : SysuiTestCase() { ) } + private fun setOnKeyguard() { + setShadeState(StatusBarState.KEYGUARD) + } + + private fun setOnLockedShade() { + setShadeState(StatusBarState.SHADE_LOCKED) + } + + private fun setOnUnlockedShade() { + setShadeState(StatusBarState.SHADE) + } + + private fun setShadeState(state: Int) { + whenever(statusBarStateController.state).thenReturn(state) + whenever(statusBarStateController.currentOrUpcomingState).thenReturn(state) + } + companion object { private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f private val DEFAULT_EXPANSION_EVENT = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt index 5ca15bb93d31..aafd871f72f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt @@ -1,4 +1,4 @@ -package com.android.systemui.statusbar.phone.shade.transition +package com.android.systemui.shade.transition import android.testing.AndroidTestingRunner import android.testing.TestableLooper diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt new file mode 100644 index 000000000000..aaa2357a1c52 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -0,0 +1,209 @@ +/* + * 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.shared.clocks + +import org.mockito.Mockito.`when` as whenever +import android.content.Context +import android.content.ContentResolver +import android.graphics.drawable.Drawable +import android.os.Handler +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.PluginListener +import com.android.systemui.shared.plugins.PluginManager +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.eq +import junit.framework.Assert.assertEquals +import junit.framework.Assert.fail +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ClockRegistryTest : SysuiTestCase() { + + @JvmField @Rule val mockito = MockitoJUnit.rule() + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockPluginManager: PluginManager + @Mock private lateinit var mockClock: Clock + @Mock private lateinit var mockThumbnail: Drawable + @Mock private lateinit var mockHandler: Handler + @Mock private lateinit var mockContentResolver: ContentResolver + private lateinit var pluginListener: PluginListener<ClockProviderPlugin> + private lateinit var registry: ClockRegistry + + private var settingValue: String = "" + + companion object { + private fun failFactory(): Clock { + fail("Unexpected call to createClock") + return null!! + } + + private fun failThumbnail(): Drawable? { + fail("Unexpected call to getThumbnail") + return null + } + } + + private class FakeClockPlugin : ClockProviderPlugin { + private val metadata = mutableListOf<ClockMetadata>() + private val createCallbacks = mutableMapOf<ClockId, () -> Clock>() + private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>() + + override fun getClocks() = metadata + override fun createClock(id: ClockId): Clock = createCallbacks[id]!!() + override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!() + + fun addClock( + id: ClockId, + name: String, + create: () -> Clock = ::failFactory, + getThumbnail: () -> Drawable? = ::failThumbnail + ) { + metadata.add(ClockMetadata(id, name)) + createCallbacks[id] = create + thumbnailCallbacks[id] = getThumbnail + } + } + + @Before + fun setUp() { + whenever(mockContext.contentResolver).thenReturn(mockContentResolver) + + val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>() + registry = object : ClockRegistry(mockContext, mockPluginManager, mockHandler) { + override var currentClockId: ClockId + get() = settingValue + set(value) { settingValue = value } + } + verify(mockPluginManager).addPluginListener(captor.capture(), + eq(ClockProviderPlugin::class.java)) + pluginListener = captor.value + } + + @Test + fun pluginRegistration_CorrectState() { + val plugin1 = FakeClockPlugin() + plugin1.addClock("clock_1", "clock 1") + plugin1.addClock("clock_2", "clock 2") + + val plugin2 = FakeClockPlugin() + plugin2.addClock("clock_3", "clock 3") + plugin2.addClock("clock_4", "clock 4") + + pluginListener.onPluginConnected(plugin1, mockContext) + pluginListener.onPluginConnected(plugin2, mockContext) + val list = registry.getClocks() + assertEquals(list, listOf( + ClockMetadata("clock_1", "clock 1"), + ClockMetadata("clock_2", "clock 2"), + ClockMetadata("clock_3", "clock 3"), + ClockMetadata("clock_4", "clock 4") + )) + } + + @Test + fun clockIdConflict_ErrorWithoutCrash() { + val plugin1 = FakeClockPlugin() + plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail }) + plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail }) + + val plugin2 = FakeClockPlugin() + plugin2.addClock("clock_1", "clock 1") + plugin2.addClock("clock_2", "clock 2") + + pluginListener.onPluginConnected(plugin1, mockContext) + pluginListener.onPluginConnected(plugin2, mockContext) + val list = registry.getClocks() + assertEquals(list, listOf( + ClockMetadata("clock_1", "clock 1"), + ClockMetadata("clock_2", "clock 2") + )) + + assertEquals(registry.createExampleClock("clock_1"), mockClock) + assertEquals(registry.createExampleClock("clock_2"), mockClock) + assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail) + assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail) + } + + @Test + fun createCurrentClock_pluginConnected() { + val plugin1 = FakeClockPlugin() + plugin1.addClock("clock_1", "clock 1") + plugin1.addClock("clock_2", "clock 2") + + settingValue = "clock_3" + val plugin2 = FakeClockPlugin() + plugin2.addClock("clock_3", "clock 3", { mockClock }) + plugin2.addClock("clock_4", "clock 4") + + pluginListener.onPluginConnected(plugin1, mockContext) + pluginListener.onPluginConnected(plugin2, mockContext) + + val clock = registry.createCurrentClock() + assertEquals(clock, mockClock) + } + + @Test + fun createDefaultClock_pluginDisconnected() { + val plugin1 = FakeClockPlugin() + plugin1.addClock(DEFAULT_CLOCK_ID, "default", { mockClock }) + plugin1.addClock("clock_2", "clock 2") + + settingValue = "clock_3" + val plugin2 = FakeClockPlugin() + plugin2.addClock("clock_3", "clock 3") + plugin2.addClock("clock_4", "clock 4") + + pluginListener.onPluginConnected(plugin1, mockContext) + pluginListener.onPluginConnected(plugin2, mockContext) + pluginListener.onPluginDisconnected(plugin2) + + val clock = registry.createCurrentClock() + assertEquals(clock, mockClock) + } + + @Test + fun pluginRemoved_clockChanged() { + val plugin1 = FakeClockPlugin() + plugin1.addClock("clock_1", "clock 1") + plugin1.addClock("clock_2", "clock 2") + + settingValue = "clock_3" + val plugin2 = FakeClockPlugin() + plugin2.addClock("clock_3", "clock 3", { mockClock }) + plugin2.addClock("clock_4", "clock 4") + + pluginListener.onPluginConnected(plugin1, mockContext) + pluginListener.onPluginConnected(plugin2, mockContext) + + var changeCallCount = 0 + registry.registerClockChangeListener({ changeCallCount++ }) + + pluginListener.onPluginDisconnected(plugin1) + assertEquals(0, changeCallCount) + + pluginListener.onPluginDisconnected(plugin2) + assertEquals(1, changeCallCount) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 562c97017862..fe1cd978ef91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -1,6 +1,5 @@ package com.android.systemui.statusbar -import org.mockito.Mockito.`when` as whenever import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -14,6 +13,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.media.MediaHierarchyManager import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QS +import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.AmbientState @@ -22,7 +22,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.LSShadeTransitionLogger -import com.android.systemui.statusbar.phone.NotificationPanelViewController import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.FakeConfigurationController import org.junit.After @@ -45,6 +44,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit private fun <T> anyObject(): T { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 16b0376ba1f9..aeef6b04108e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; +import static android.service.notification.NotificationStats.DISMISSAL_SHADE; +import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; @@ -41,6 +43,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.ActivityManager; @@ -50,6 +53,7 @@ import android.app.PendingIntent; import android.content.Intent; import android.graphics.drawable.Icon; import android.os.Handler; +import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; @@ -91,7 +95,9 @@ import com.android.systemui.statusbar.notification.row.NotificationEntryManagerI import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -138,9 +144,14 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private NotificationMediaManager mNotificationMediaManager; @Mock private NotificationRowBinder mNotificationRowBinder; @Mock private NotificationListener mNotificationListener; + @Mock private IStatusBarService mStatusBarService; + + private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); + private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock); private int mId; private NotificationEntry mEntry; + private DismissedByUserStats mStats; private StatusBarNotification mSbn; private NotificationEntryManager mEntryManager; @@ -191,6 +202,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { Handler.createAsync(TestableLooper.get(this).getLooper())); mEntry = createNotification(); + mStats = defaultStats(mEntry); mSbn = mEntry.getSbn(); when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false); @@ -201,9 +213,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase { () -> mNotificationRowBinder, () -> mRemoteInputManager, mLeakDetector, - mock(IStatusBarService.class), + mStatusBarService, NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(), - mock(DumpManager.class) + mock(DumpManager.class), + mBgExecutor ); mEntryManager.initialize( mNotificationListener, @@ -316,6 +329,31 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + public void testPerformRemoveNotification_sendRemovalToServer() throws RemoteException { + // GIVEN an entry manager with a notification + mEntryManager.addActiveNotificationForTest(mEntry); + + // GIVEN interceptor that doesn't intercept + when(mRemoveInterceptor.onNotificationRemoveRequested( + eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt())) + .thenReturn(false); + + // WHEN the notification entry is removed + mEntryManager.performRemoveNotification(mSbn, mStats, REASON_CANCEL); + + // THEN notification removal is sent to the server + FakeExecutor.exhaustExecutors(mBgExecutor); + verify(mStatusBarService).onNotificationClear( + mSbn.getPackageName(), + mSbn.getUser().getIdentifier(), + mSbn.getKey(), + mStats.dismissalSurface, + mStats.dismissalSentiment, + mStats.notificationVisibility); + verifyNoMoreInteractions(mStatusBarService); + } + + @Test public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() { mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON); @@ -573,23 +611,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON)); } - private NotificationEntry createNotification() { - Notification.Builder n = new Notification.Builder(mContext, "id") - .setSmallIcon(R.drawable.ic_person) - .setContentTitle("Title") - .setContentText("Text"); - - return new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setId(mId++) - .setNotification(n.build()) - .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT)) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .build(); - } - /* Tests annexed from NotificationDataTest go here */ @Test @@ -713,4 +734,28 @@ public class NotificationEntryManagerTest extends SysuiTestCase { return mManagedNotifs.contains(notificationKey); } } + + private NotificationEntry createNotification() { + Notification.Builder n = new Notification.Builder(mContext, "id") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + + return new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setId(mId++) + .setNotification(n.build()) + .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT)) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + } + + private static DismissedByUserStats defaultStats(NotificationEntry entry) { + return new DismissedByUserStats( + DISMISSAL_SHADE, + DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt index 9f82a5673c6e..f4458bbdc497 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt @@ -6,11 +6,11 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase +import com.android.systemui.shade.NotificationShadeWindowViewController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.HeadsUpManagerPhone -import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController import com.android.systemui.statusbar.policy.HeadsUpUtil import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue @@ -19,8 +19,8 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index ef763d9f8b30..4df99be9efd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -92,6 +92,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; +import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; @@ -146,6 +147,7 @@ public class NotifCollectionTest extends SysuiTestCase { private NoManSimulator mNoMan; private FakeSystemClock mClock = new FakeSystemClock(); + private FakeExecutor mBgExecutor = new FakeExecutor(mClock); @Before public void setUp() { @@ -162,6 +164,7 @@ public class NotifCollectionTest extends SysuiTestCase { mNotifPipelineFlags, mLogger, mMainHandler, + mBgExecutor, mEulogizer, mock(DumpManager.class)); mCollection.attach(mGroupCoalescer); @@ -461,6 +464,8 @@ public class NotifCollectionTest extends SysuiTestCase { DismissedByUserStats stats = defaultStats(entry2); mCollection.dismissNotification(entry2, defaultStats(entry2)); + FakeExecutor.exhaustExecutors(mBgExecutor); + // THEN we send the dismissal to system server verify(mStatusBarService).onNotificationClear( notif2.sbn.getPackageName(), @@ -674,6 +679,8 @@ public class NotifCollectionTest extends SysuiTestCase { mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, stats); + FakeExecutor.exhaustExecutors(mBgExecutor); + // THEN we send the dismissal to system server verify(mStatusBarService).onNotificationClear( eq(notif.sbn.getPackageName()), @@ -1211,6 +1218,7 @@ public class NotifCollectionTest extends SysuiTestCase { new Pair<>(entry2, defaultStats(entry2)))); // THEN we send the dismissals to system server + FakeExecutor.exhaustExecutors(mBgExecutor); verify(mStatusBarService).onNotificationClear( notif1.sbn.getPackageName(), notif1.sbn.getUser().getIdentifier(), @@ -1577,6 +1585,7 @@ public class NotifCollectionTest extends SysuiTestCase { // WHEN finally dismissing onDismiss.run(); + FakeExecutor.exhaustExecutors(mBgExecutor); verify(mStatusBarService).onNotificationClear(any(), anyInt(), eq(notifEvent.key), anyInt(), anyInt(), any()); verifyNoMoreInteractions(mStatusBarService); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index 96c40ecd3d81..c961cec39208 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -36,6 +36,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotifPanelEvents; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -44,7 +45,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; -import com.android.systemui.statusbar.phone.NotifPanelEvents; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 54cbe24df732..72d3c2e95d75 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -25,6 +25,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -96,6 +98,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotifPipelineFlags mFlags; @Mock KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; + @Mock + PendingIntent mPendingIntent; private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider; @@ -197,7 +201,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { ensureStateForHeadsUpWhenAwake(); // WHEN this entry should be filtered out - NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); + NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); when(mNotificationFilter.shouldFilterOut(entry)).thenReturn(true); // THEN we shouldn't heads up this entry @@ -207,7 +211,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @Test public void testDoNotRunFilterOnNewPipeline() { // WHEN this entry should be filtered out - NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); + NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); mNotifInterruptionStateProvider.shouldHeadsUp(entry); verify(mNotificationFilter, times(0)).shouldFilterOut(eq(entry)); } @@ -422,6 +426,122 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } + @Test + public void testShouldNotFullScreen_notPendingIntent() throws RemoteException { + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger, never()).logFullscreen(any(), any()); + } + + @Test + public void testShouldNotFullScreen_notHighImportance() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + verify(mLogger).logNoFullscreen(entry, "Not important enough"); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger, never()).logFullscreen(any(), any()); + } + + @Test + public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true); + when(mPowerManager.isInteractive()).thenReturn(false); + when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN"); + verify(mLogger, never()).logFullscreen(any(), any()); + } + + @Test + public void testShouldFullScreen_notInteractive() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(false); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger).logFullscreen(entry, "Device is not interactive"); + } + + @Test + public void testShouldFullScreen_isDreaming() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger).logFullscreen(entry, "Device is dreaming"); + } + + @Test + public void testShouldFullScreen_onKeyguard() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger).logFullscreen(entry, "Keyguard is showing"); + } + + @Test + public void testShouldNotFullScreen_willHun() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mPowerManager.isScreenOn()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + verify(mLogger).logNoFullscreen(entry, "Expected to HUN"); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger, never()).logFullscreen(any(), any()); + } + + @Test + public void testShouldFullScreen_packageSnoozed() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mPowerManager.isScreenOn()).thenReturn(true); + when(mDreamManager.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + when(mHeadsUpManager.isSnoozed("a")).thenReturn(true); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + verify(mLogger).logNoHeadsUpPackageSnoozed(entry); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger).logFullscreen(entry, "Expected not to HUN"); + } + /** * Bubbles can happen. */ @@ -516,8 +636,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { private NotificationEntry createBubble(String groupKey, Integer groupAlert) { Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder( PendingIntent.getActivity(mContext, 0, new Intent(), - PendingIntent.FLAG_MUTABLE), - Icon.createWithResource(mContext.getResources(), R.drawable.android)) + PendingIntent.FLAG_MUTABLE), + Icon.createWithResource(mContext.getResources(), R.drawable.android)) .build(); Notification.Builder nb = new Notification.Builder(getContext(), "a") .setContentTitle("title") @@ -549,6 +669,10 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .setContentText("content text") .build(); + return createNotification(importance, n); + } + + private NotificationEntry createNotification(int importance, Notification n) { return new NotificationEntryBuilder() .setPkg("a") .setOpPkg("a") @@ -559,45 +683,57 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .build(); } + private NotificationEntry createFsiNotification(int importance, boolean silent) { + Notification n = new Notification.Builder(getContext(), "a") + .setContentTitle("title") + .setContentText("content text") + .setFullScreenIntent(mPendingIntent, true) + .setGroup("fsi") + .setGroupAlertBehavior(silent ? GROUP_ALERT_SUMMARY : Notification.GROUP_ALERT_ALL) + .build(); + + return createNotification(importance, n); + } + private final NotificationInterruptSuppressor mSuppressAwakeHeadsUp = new NotificationInterruptSuppressor() { - @Override - public String getName() { - return "suppressAwakeHeadsUp"; - } + @Override + public String getName() { + return "suppressAwakeHeadsUp"; + } - @Override - public boolean suppressAwakeHeadsUp(NotificationEntry entry) { - return true; - } - }; + @Override + public boolean suppressAwakeHeadsUp(NotificationEntry entry) { + return true; + } + }; private final NotificationInterruptSuppressor mSuppressAwakeInterruptions = new NotificationInterruptSuppressor() { - @Override - public String getName() { - return "suppressAwakeInterruptions"; - } + @Override + public String getName() { + return "suppressAwakeInterruptions"; + } - @Override - public boolean suppressAwakeInterruptions(NotificationEntry entry) { - return true; - } - }; + @Override + public boolean suppressAwakeInterruptions(NotificationEntry entry) { + return true; + } + }; private final NotificationInterruptSuppressor mSuppressInterruptions = new NotificationInterruptSuppressor() { - @Override - public String getName() { - return "suppressInterruptions"; - } - - @Override - public boolean suppressInterruptions(NotificationEntry entry) { - return true; - } - }; + @Override + public String getName() { + return "suppressInterruptions"; + } + + @Override + public boolean suppressInterruptions(NotificationEntry entry) { + return true; + } + }; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index 251ac7d250fe..bf7549a23707 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -194,7 +194,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { mLeakDetector, mock(IStatusBarService.class), NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(), - mock(DumpManager.class) + mock(DumpManager.class), + mBgExecutor ); mEntryManager.initialize( mNotificationListener, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index f42e6fb2d186..c199147b4f79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -52,6 +52,7 @@ import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; @@ -75,7 +76,6 @@ import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ZenModeController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index 9bfb2c4ce00c..d79f3361408b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -37,6 +37,8 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.StatusBarStateControllerImpl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 74fb7f67940d..b75c52ad283e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -105,6 +105,10 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.shade.NotificationPanelView; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 26ac70c70e7f..5c9871a01536 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -41,6 +41,8 @@ import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index ed22cd3c55fc..103b7b4268de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 4e1a7088b17f..11e502fc79bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -49,6 +49,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconListTest.java index 4c20b61083f7..f0a4f3f2bf7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconListTest.java @@ -1,4 +1,20 @@ -package com.android.systemui.statusbar; +/* + * 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.phone; import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY; @@ -9,13 +25,10 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.test.suitebuilder.annotation.SmallTest; - +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.phone.StatusBarIconHolder; -import com.android.systemui.statusbar.phone.StatusBarIconList; import com.android.systemui.statusbar.phone.StatusBarIconList.Slot; import org.junit.Test; @@ -33,28 +46,39 @@ public class StatusBarIconListTest extends SysuiTestCase { @Test public void testGetExistingSlot() { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); - assertEquals(1, statusBarIconList.getSlotIndex("bbb")); - assertEquals(2, statusBarIconList.getSlotIndex("ccc")); + + List<Slot> slots = statusBarIconList.getSlots(); + assertEquals(3, slots.size()); + assertEquals("aaa", slots.get(0).getName()); + assertEquals("bbb", slots.get(1).getName()); + assertEquals("ccc", slots.get(2).getName()); } @Test public void testGetNonexistingSlot() { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); - assertEquals(0, statusBarIconList.getSlotIndex("aaa")); - assertEquals(3, statusBarIconList.size()); - assertEquals(0, statusBarIconList.getSlotIndex("zzz")); // new content added in front - assertEquals(1, statusBarIconList.getSlotIndex("aaa")); // slid back - assertEquals(4, statusBarIconList.size()); + + statusBarIconList.getSlot("zzz"); + + List<Slot> slots = statusBarIconList.getSlots(); + assertEquals(4, slots.size()); + // new content added in front, so zzz should be first and aaa should slide back to second + assertEquals("zzz", slots.get(0).getName()); + assertEquals("aaa", slots.get(1).getName()); } @Test public void testAddSlotSlidesIcons() { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class); - statusBarIconList.setIcon(0, sbHolder); - statusBarIconList.getSlotIndex("zzz"); // new content added in front - assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY)); - assertEquals(sbHolder, statusBarIconList.getIcon(1, TAG_PRIMARY)); + statusBarIconList.setIcon("aaa", sbHolder); + + statusBarIconList.getSlot("zzz"); + + List<Slot> slots = statusBarIconList.getSlots(); + // new content added in front, so the holder we set on "aaa" should show up at index 1 + assertNull(slots.get(0).getHolderForTag(TAG_PRIMARY)); + assertEquals(sbHolder, slots.get(1).getHolderForTag(TAG_PRIMARY)); } @Test @@ -62,11 +86,13 @@ public class StatusBarIconListTest extends SysuiTestCase { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class); StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class); - statusBarIconList.setIcon(0, sbHolderA); - statusBarIconList.setIcon(1, sbHolderB); - assertEquals(sbHolderA, statusBarIconList.getIcon(0, TAG_PRIMARY)); - assertEquals(sbHolderB, statusBarIconList.getIcon(1, TAG_PRIMARY)); - assertNull(statusBarIconList.getIcon(2, TAG_PRIMARY)); // icon not set + + statusBarIconList.setIcon("aaa", sbHolderA); + statusBarIconList.setIcon("bbb", sbHolderB); + + assertEquals(sbHolderA, statusBarIconList.getIconHolder("aaa", TAG_PRIMARY)); + assertEquals(sbHolderB, statusBarIconList.getIconHolder("bbb", TAG_PRIMARY)); + assertNull(statusBarIconList.getIconHolder("ccc", TAG_PRIMARY)); // icon not set } @Test @@ -74,24 +100,31 @@ public class StatusBarIconListTest extends SysuiTestCase { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class); StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class); - statusBarIconList.setIcon(0, sbHolderA); - statusBarIconList.setIcon(1, sbHolderB); - statusBarIconList.removeIcon(0, TAG_PRIMARY); - assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY)); // icon not set + + statusBarIconList.setIcon("aaa", sbHolderA); + statusBarIconList.setIcon("bbb", sbHolderB); + + statusBarIconList.removeIcon("aaa", TAG_PRIMARY); + + assertNull(statusBarIconList.getIconHolder("aaa", TAG_PRIMARY)); // icon not set } @Test public void testGetViewIndex_NoMultiples() { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class); - statusBarIconList.setIcon(2, sbHolder); - // Icon for item 2 is 0th child view. - assertEquals(0, statusBarIconList.getViewIndex(2, TAG_PRIMARY)); - statusBarIconList.setIcon(0, sbHolder); - // Icon for item 0 is 0th child view, - assertEquals(0, statusBarIconList.getViewIndex(0, TAG_PRIMARY)); - // and item 2 is now 1st child view. - assertEquals(1, statusBarIconList.getViewIndex(2, TAG_PRIMARY)); + + statusBarIconList.setIcon("ccc", sbHolder); + + // Since only "ccc" has a holder set, it should be first + assertEquals(0, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY)); + + // Now, also set a holder for "aaa" + statusBarIconList.setIcon("aaa", sbHolder); + + // Then "aaa" gets the first view index and "ccc" gets the second + assertEquals(0, statusBarIconList.getViewIndex("aaa", TAG_PRIMARY)); + assertEquals(1, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY)); } @Test @@ -99,7 +132,7 @@ public class StatusBarIconListTest extends SysuiTestCase { StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS); StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class); - statusBarIconList.setIcon(2, sbHolder); // item 2, one icon 0th child + statusBarIconList.setIcon("ccc", sbHolder); // All of these can be added to the same slot // no tag bc it defaults to 0 @@ -111,23 +144,23 @@ public class StatusBarIconListTest extends SysuiTestCase { int sb4Tag = 2; when(sbHolder4.getTag()).thenReturn(sb4Tag); - // Put a holder at slot 1, verify that it is first - statusBarIconList.setIcon(1, sbHolder2); - assertEquals(0, statusBarIconList.getViewIndex(1, TAG_PRIMARY)); - - // Put another holder at slot 1, verify it's index 0 and the rest come after - statusBarIconList.setIcon(1, sbHolder3); - assertEquals(0, statusBarIconList.getViewIndex(1, sb3Tag)); - assertEquals(1, statusBarIconList.getViewIndex(1, TAG_PRIMARY)); - // First icon should be at the end - assertEquals(2, statusBarIconList.getViewIndex(2, TAG_PRIMARY)); - - // Put another one in there just for good measure - statusBarIconList.setIcon(1, sbHolder4); - assertEquals(0, statusBarIconList.getViewIndex(1, sb4Tag)); - assertEquals(1, statusBarIconList.getViewIndex(1, sb3Tag)); - assertEquals(2, statusBarIconList.getViewIndex(1, TAG_PRIMARY)); - assertEquals(3, statusBarIconList.getViewIndex(2, TAG_PRIMARY)); + // Put a holder for "bbb", verify that it is first + statusBarIconList.setIcon("bbb", sbHolder2); + assertEquals(0, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY)); + + // Put another holder for "bbb" at slot 1, verify its index 0 and the rest come after + statusBarIconList.setIcon("bbb", sbHolder3); + assertEquals(0, statusBarIconList.getViewIndex("bbb", sb3Tag)); + assertEquals(1, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY)); + // "ccc" should appear at the end + assertEquals(2, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY)); + + // Put another one in "bbb" just for good measure + statusBarIconList.setIcon("bbb", sbHolder4); + assertEquals(0, statusBarIconList.getViewIndex("bbb", sb4Tag)); + assertEquals(1, statusBarIconList.getViewIndex("bbb", sb3Tag)); + assertEquals(2, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY)); + assertEquals(3, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY)); } /** @@ -172,4 +205,4 @@ public class StatusBarIconListTest extends SysuiTestCase { return true; } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index aa08440d8cbc..79fce82a1bcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -47,6 +47,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index f53f5fb2aebd..7046150ba373 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -59,6 +59,8 @@ import com.android.systemui.assist.AssistManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 0bfd1aed6b1e..4b5d1f747ca0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -38,6 +38,8 @@ import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 011279721fd2..746c92e485b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -27,6 +27,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.StatusBarStateControllerImpl import com.android.systemui.statusbar.policy.KeyguardStateController @@ -39,12 +40,12 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.`when` import org.mockito.Mockito.anyLong import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -184,4 +185,4 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { controller.startAnimation() assertFalse(controller.isAnimationPlaying()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 5346aeb3b766..681e998ac9e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -46,6 +46,7 @@ import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogcatEchoTracker; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.OperatorNameViewController; @@ -53,7 +54,6 @@ import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; @@ -124,7 +124,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); assertEquals(View.VISIBLE, getClockView().getVisibility()); } @@ -134,11 +134,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); - assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @Test @@ -234,7 +234,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); assertEquals(View.VISIBLE, getClockView().getVisibility()); } @@ -246,7 +246,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); assertEquals(View.VISIBLE, getClockView().getVisibility()); } @@ -257,12 +257,12 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); // Make sure they start out as visible - assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); assertEquals(View.VISIBLE, getClockView().getVisibility()); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); assertEquals(View.GONE, getClockView().getVisibility()); } @@ -273,14 +273,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); // Make sure they start out as visible - assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); assertEquals(View.VISIBLE, getClockView().getVisibility()); fragment.onDozingChanged(true); // When this callback is triggered, we want to make sure the clock and system info // visibilities are recalculated. Since dozing=true, they shouldn't be visible. - assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); assertEquals(View.GONE, getClockView().getVisibility()); } @@ -419,7 +419,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { return mFragment.getView().findViewById(R.id.clock); } - private View getSystemIconAreaView() { - return mFragment.getView().findViewById(R.id.system_icon_area); + private View getEndSideContentView() { + return mFragment.getView().findViewById(R.id.status_bar_end_side_content); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index 1af8a77b1610..09d7c03c2091 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -54,7 +54,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.phone.NotificationShadeWindowView +import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.telephony.TelephonyListenerManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 2a7feb02abf7..6a0124a3be8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -85,6 +85,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; @@ -105,7 +106,6 @@ import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl; -import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; @@ -133,6 +133,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.sysui.ShellController; import org.junit.Before; import org.junit.Ignore; @@ -214,6 +215,8 @@ public class BubblesTest extends SysuiTestCase { private BubbleEntry mBubbleEntry2User11; @Mock + private ShellController mShellController; + @Mock private Bubbles.BubbleExpandListener mBubbleExpandListener; @Mock private PendingIntent mDeleteIntent; @@ -328,6 +331,7 @@ public class BubblesTest extends SysuiTestCase { when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor); mBubbleController = new TestableBubbleController( mContext, + mShellController, mBubbleData, mFloatingContentCoordinator, mDataRepository, @@ -356,7 +360,6 @@ public class BubblesTest extends SysuiTestCase { mNotificationShadeWindowController, mock(KeyguardStateController.class), mShadeController, - mConfigurationController, mStatusBarService, mock(INotificationManager.class), mVisibilityProvider, @@ -378,6 +381,11 @@ public class BubblesTest extends SysuiTestCase { } @Test + public void instantiateController_registerConfigChangeListener() { + verify(mShellController, times(1)).addConfigurationChangeListener(any()); + } + + @Test public void testAddBubble() { mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java index 17e5778f7aab..f901c327b76e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java @@ -38,6 +38,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.sysui.ShellController; import java.util.Optional; @@ -48,6 +49,7 @@ public class TestableBubbleController extends BubbleController { // Let's assume surfaces can be synchronized immediately. TestableBubbleController(Context context, + ShellController shellController, BubbleData data, FloatingContentCoordinator floatingContentCoordinator, BubbleDataRepository dataRepository, @@ -67,11 +69,12 @@ public class TestableBubbleController extends BubbleController { Handler shellMainHandler, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { - super(context, data, Runnable::run, floatingContentCoordinator, dataRepository, - statusBarService, windowManager, windowManagerShellWrapper, userManager, - launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, - displayController, oneHandedOptional, dragAndDropController, shellMainExecutor, - shellMainHandler, new SyncExecutor(), taskViewTransitions, syncQueue); + super(context, shellController, data, Runnable::run, floatingContentCoordinator, + dataRepository, statusBarService, windowManager, windowManagerShellWrapper, + userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, + positioner, displayController, oneHandedOptional, dragAndDropController, + shellMainExecutor, shellMainHandler, new SyncExecutor(), taskViewTransitions, + syncQueue); setInflateSynchronously(true); initialize(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 185942e6fbc8..72ade2632078 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -24,12 +24,10 @@ import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; -import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -37,14 +35,12 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.compatui.CompatUI; -import com.android.wm.shell.draganddrop.DragAndDrop; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.sysui.ShellInterface; import org.junit.Before; import org.junit.Test; @@ -65,35 +61,30 @@ import java.util.Optional; public class WMShellTest extends SysuiTestCase { WMShell mWMShell; + @Mock ShellInterface mShellInterface; @Mock CommandQueue mCommandQueue; @Mock ConfigurationController mConfigurationController; @Mock KeyguardStateController mKeyguardStateController; @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock NavigationModeController mNavigationModeController; @Mock ScreenLifecycle mScreenLifecycle; @Mock SysUiState mSysUiState; @Mock Pip mPip; @Mock SplitScreen mSplitScreen; @Mock OneHanded mOneHanded; - @Mock HideDisplayCutout mHideDisplayCutout; @Mock WakefulnessLifecycle mWakefulnessLifecycle; @Mock ProtoTracer mProtoTracer; @Mock ShellCommandHandler mShellCommandHandler; - @Mock CompatUI mCompatUI; @Mock UserInfoController mUserInfoController; @Mock ShellExecutor mSysUiMainExecutor; - @Mock DragAndDrop mDragAndDrop; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mWMShell = new WMShell(mContext, Optional.of(mPip), - Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), - Optional.of(mShellCommandHandler), Optional.of(mCompatUI), - Optional.of(mDragAndDrop), - mCommandQueue, mConfigurationController, mKeyguardStateController, - mKeyguardUpdateMonitor, mNavigationModeController, mScreenLifecycle, mSysUiState, + mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip), + Optional.of(mSplitScreen), Optional.of(mOneHanded), + Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController, + mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor); } @@ -105,35 +96,12 @@ public class WMShellTest extends SysuiTestCase { } @Test - public void initSplitScreen_registersCallbacks() { - mWMShell.initSplitScreen(mSplitScreen); - - verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class)); - } - - @Test public void initOneHanded_registersCallbacks() { mWMShell.initOneHanded(mOneHanded); - verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class)); verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class)); verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class)); verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class)); verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class)); } - - @Test - public void initHideDisplayCutout_registersCallbacks() { - mWMShell.initHideDisplayCutout(mHideDisplayCutout); - - verify(mConfigurationController).addCallback( - any(ConfigurationController.ConfigurationListener.class)); - } - - @Test - public void initCompatUI_registersCallbacks() { - mWMShell.initCompatUi(mCompatUI); - - verify(mKeyguardStateController).addCallback(any(KeyguardStateController.Callback.class)); - } } diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java index 6b235879486c..f523773033d1 100644 --- a/services/companion/java/com/android/server/companion/PackageUtils.java +++ b/services/companion/java/com/android/server/companion/PackageUtils.java @@ -67,9 +67,11 @@ final class PackageUtils { String requiredFeature = FEATURE_COMPANION_DEVICE_SETUP; FeatureInfo[] requestedFeatures = getPackageInfo(context, userId, packageName).reqFeatures; - for (int i = 0; i < requestedFeatures.length; i++) { - if (requiredFeature.equals(requestedFeatures[i].name)) { - return; + if (requestedFeatures != null) { + for (int i = 0; i < requestedFeatures.length; i++) { + if (requiredFeature.equals(requestedFeatures[i].name)) { + return; + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 00d728ba0fef..07a5fb5d0f0f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -14389,6 +14389,19 @@ public class ActivityManagerService extends IActivityManager.Stub final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); + // Non-system callers can't declare that a broadcast is alarm-related. + // The PendingIntent invocation case is handled in PendingIntentRecord. + if (bOptions != null && callingUid != SYSTEM_UID) { + if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) { + if (DEBUG_BROADCAST) { + Slog.w(TAG, "Non-system caller " + callingUid + + " may not flag broadcast as alarm-related"); + } + throw new SecurityException( + "Non-system callers may not flag broadcasts as alarm-related"); + } + } + final long origId = Binder.clearCallingIdentity(); try { return broadcastIntentLocked(callerApp, @@ -14402,6 +14415,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + // Not the binder call surface int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid, int realCallingUid, int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 19ffc1733f3d..ae91d75ef0ce 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -70,6 +70,7 @@ final class BroadcastRecord extends Binder { final boolean callerInstantApp; // caller is an Instant App? final boolean ordered; // serialize the send to receivers? final boolean sticky; // originated from existing sticky data? + final boolean alarm; // originated from an alarm triggering? final boolean initialSticky; // initial broadcast from register to sticky? final int userId; // user id this broadcast was for final String resolvedType; // the resolved data type @@ -305,6 +306,7 @@ final class BroadcastRecord extends Binder { this.allowBackgroundActivityStarts = allowBackgroundActivityStarts; mBackgroundActivityStartsToken = backgroundActivityStartsToken; this.timeoutExempt = timeoutExempt; + alarm = options != null && options.isAlarmBroadcast(); } /** @@ -357,6 +359,7 @@ final class BroadcastRecord extends Binder { allowBackgroundActivityStarts = from.allowBackgroundActivityStarts; mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken; timeoutExempt = from.timeoutExempt; + alarm = from.alarm; } /** diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 4044cceb606b..bda60ff2172b 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.ActivityManager.START_SUCCESS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -34,6 +35,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.PowerWhitelistManager; import android.os.PowerWhitelistManager.ReasonCode; +import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.TransactionTooLargeException; @@ -416,6 +418,22 @@ public final class PendingIntentRecord extends IIntentSender.Stub { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); + + // Only system senders can declare a broadcast to be alarm-originated. We check + // this here rather than in the general case handling below to fail before the other + // invocation side effects such as allowlisting. + if (options != null && callingUid != Process.SYSTEM_UID + && key.type == ActivityManager.INTENT_SENDER_BROADCAST) { + if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) { + if (DEBUG_BROADCAST_LIGHT) { + Slog.w(TAG, "Non-system caller " + callingUid + + " may not flag broadcast as alarm-related"); + } + throw new SecurityException( + "Non-system callers may not flag broadcasts as alarm-related"); + } + } + final long origId = Binder.clearCallingIdentity(); int res = START_SUCCESS; diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java index ad24cf0591ca..262be08b60e2 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java @@ -28,8 +28,10 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.OperationContext; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; +import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.Utils; @@ -41,6 +43,10 @@ public class BiometricLogger { public static final String TAG = "BiometricLogger"; public static final boolean DEBUG = false; + private static final Object sLock = new Object(); + + @GuardedBy("sLock") + private static int sAlsCounter; private final int mStatsModality; private final int mStatsAction; @@ -345,13 +351,33 @@ public class BiometricLogger { if (!mLightSensorEnabled) { mLightSensorEnabled = true; mLastAmbientLux = 0; - mSensorManager.registerListener(mLightSensorListener, lightSensor, - SensorManager.SENSOR_DELAY_NORMAL); + int localAlsCounter; + synchronized (sLock) { + localAlsCounter = sAlsCounter++; + } + + if (localAlsCounter == 0) { + mSensorManager.registerListener(mLightSensorListener, lightSensor, + SensorManager.SENSOR_DELAY_NORMAL); + } else { + Slog.e(TAG, "Ignoring request to subscribe to ALSProbe due to non-zero ALS" + + " counter: " + localAlsCounter); + Slog.e(TAG, Log.getStackTraceString(new Throwable())); + } } } else { mLightSensorEnabled = false; mLastAmbientLux = 0; mSensorManager.unregisterListener(mLightSensorListener); + int localAlsCounter; + synchronized (sLock) { + localAlsCounter = --sAlsCounter; + } + if (localAlsCounter != 0) { + Slog.e(TAG, "Non-zero ALS counter after unsubscribing from ALSProbe: " + + localAlsCounter); + Slog.e(TAG, Log.getStackTraceString(new Throwable())); + } } } } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 90b9967eef59..d14902eaf8f5 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -522,10 +522,12 @@ final class LogicalDisplay { // Set the layer stack. device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack); // Also inform whether the device is the same one sent to inputflinger for its layerstack. + // Prevent displays that are disabled from receiving input. // TODO(b/188914255): Remove once input can dispatch against device vs layerstack. device.setDisplayFlagsLocked(t, - device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE - ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0); + (isEnabled() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE) + ? SurfaceControl.DISPLAY_RECEIVES_INPUT + : 0); // Set the color mode and allowed display mode. if (device == mPrimaryDisplayDevice) { diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 63c5456c972b..7b60345caf87 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.TaskInfo; @@ -45,6 +46,9 @@ import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.PowerManagerInternal; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; @@ -221,6 +225,10 @@ public final class DreamManagerService extends SystemService { } } + protected void requestStartDreamFromShell() { + requestDreamInternal(); + } + private void requestDreamInternal() { // Ask the power manager to nap. It will eventually call back into // startDream() if/when it is appropriate to start dreaming. @@ -275,6 +283,10 @@ public final class DreamManagerService extends SystemService { } } + protected void requestStopDreamFromShell() { + stopDreamInternal(true, "stopping dream from shell"); + } + private void stopDreamInternal(boolean immediate, String reason) { synchronized (mLock) { stopDreamLocked(immediate, reason); @@ -593,6 +605,14 @@ public final class DreamManagerService extends SystemService { } } + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new DreamShellCommand(DreamManagerService.this, mPowerManager) + .exec(this, in, out, err, args, callback, resultReceiver); + } + @Override // Binder call public ComponentName[] getDreamComponents() { return getDreamComponentsForUser(UserHandle.getCallingUserId()); diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java new file mode 100644 index 000000000000..eae7e80a89f1 --- /dev/null +++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java @@ -0,0 +1,92 @@ +/* + * 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.dreams; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.PowerManager; +import android.os.Process; +import android.os.ShellCommand; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Slog; + +import java.io.PrintWriter; + +/** + * {@link DreamShellCommand} allows accessing dream functionality, including toggling dream state. + */ +public class DreamShellCommand extends ShellCommand { + private static final boolean DEBUG = true; + private static final String TAG = "DreamShellCommand"; + private final @NonNull DreamManagerService mService; + private final @NonNull PowerManager mPowerManager; + + DreamShellCommand(@NonNull DreamManagerService service, @NonNull PowerManager powerManager) { + mService = service; + mPowerManager = powerManager; + } + + @Override + public int onCommand(String cmd) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.ROOT_UID) { + Slog.e(TAG, "Must be root before calling Dream shell commands"); + return -1; + } + + if (TextUtils.isEmpty(cmd)) { + return super.handleDefaultCommands(cmd); + } + if (DEBUG) { + Slog.d(TAG, "onCommand:" + cmd); + } + + switch (cmd) { + case "start-dreaming": + return startDreaming(); + case "stop-dreaming": + return stopDreaming(); + default: + return super.handleDefaultCommands(cmd); + } + } + + private int startDreaming() { + mPowerManager.wakeUp(SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_PLUGGED_IN, "shell:cmd:android.service.dreams:DREAM"); + mService.requestStartDreamFromShell(); + return 0; + } + + private int stopDreaming() { + mService.requestStopDreamFromShell(); + return 0; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Dream manager (dreams) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" start-dreaming"); + pw.println(" Start the currently configured dream."); + pw.println(" stop-dreaming"); + pw.println(" Stops any active dream"); + } +} diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java index e222c644da9e..d238dae634ad 100644 --- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java +++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java @@ -27,8 +27,6 @@ import android.view.InputWindowHandle; import android.view.SurfaceControl; import android.view.WindowManager; -import com.android.server.policy.WindowManagerPolicy; - /** * An internal implementation of an {@link InputMonitor} that uses a spy window. * @@ -69,9 +67,7 @@ class GestureMonitorSpyWindow { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setInputWindowInfo(mInputSurface, mWindowHandle); - // Gesture monitor should be above handwriting event surface, hence setting it to - // WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER + 1 - t.setLayer(mInputSurface, WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER + 1); + t.setLayer(mInputSurface, Integer.MAX_VALUE); t.setPosition(mInputSurface, 0, 0); t.setCrop(mInputSurface, null /* crop to parent surface */); t.show(mInputSurface); diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java index 5438faa8793e..8180e66166d9 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java @@ -27,8 +27,6 @@ import android.view.InputWindowHandle; import android.view.SurfaceControl; import android.view.WindowManager; -import com.android.server.policy.WindowManagerPolicy; - final class HandwritingEventReceiverSurface { public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName(); @@ -38,8 +36,7 @@ final class HandwritingEventReceiverSurface { // is above gesture monitors, then edge-back and swipe-up gestures won't work when this surface // is intercepting. // TODO(b/217538817): Specify the ordering in WM by usage. - private static final int HANDWRITING_SURFACE_LAYER = - WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER; + private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE - 1; private final InputWindowHandle mWindowHandle; private final InputChannel mClientChannel; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 9bc0553a7612..e8a3dcd5635f 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -155,10 +155,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { int FINISH_LAYOUT_REDO_ANIM = 0x0008; /** Layer for the screen off animation */ int COLOR_FADE_LAYER = 0x40000001; - /** Layer for Input overlays for capturing inputs for gesture detection, etc. */ - int INPUT_DISPLAY_OVERLAY_LAYER = 0x7f000000; - /** Layer for Screen Decoration: The top most visible layer just below input overlay layers */ - int SCREEN_DECOR_DISPLAY_OVERLAY_LAYER = INPUT_DISPLAY_OVERLAY_LAYER - 1; /** * Register shortcuts for window manager to dispatch. diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 1ccdaf75821f..d2a00af245f6 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -577,6 +577,21 @@ class ActivityClientController extends IActivityClientController.Stub { } } + /** + * Returns the windowing mode of the task that hosts the activity, or {@code -1} if task is not + * found. + */ + @Override + public int getTaskWindowingMode(IBinder activityToken) { + synchronized (mGlobalLock) { + final ActivityRecord ar = ActivityRecord.isInAnyTask(activityToken); + if (ar == null) { + return -1; + } + return ar.getTask().getWindowingMode(); + } + } + @Override @Nullable public IBinder getActivityTokenBelow(IBinder activityToken) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 55e6e29bcd1e..0a58044d66c6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -308,6 +308,7 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import android.view.AppTransitionAnimationSpec; +import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.InputApplicationHandle; @@ -351,7 +352,6 @@ import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot; 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; @@ -3528,7 +3528,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } final boolean isCurrentVisible = mVisibleRequested || isState(PAUSED, STARTED); - if (updateVisibility && isCurrentVisible) { + if (updateVisibility && isCurrentVisible + // Avoid intermediate lifecycle change when launching with clearing task. + && !task.isClearingToReuseTask()) { boolean ensureVisibility = false; if (occludesParent(true /* includingFinishing */)) { // If the current activity is not opaque, we need to make sure the visibilities of @@ -9612,8 +9614,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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]); + final DisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation) + .getDisplayCutout(); + policy.getNonDecorInsetsLw(rotation, cutout, mNonDecorInsets[rotation]); mStableInsets[rotation].set(mNonDecorInsets[rotation]); policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation); diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index a78dbd6bbe60..9be93404dcf1 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -19,7 +19,9 @@ package com.android.server.wm; import static android.app.ActivityManager.START_CANCELED; import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; @@ -29,6 +31,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IApplicationThread; import android.content.ComponentName; @@ -46,6 +49,7 @@ import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; import android.view.RemoteAnimationAdapter; +import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -536,6 +540,42 @@ public class ActivityStartController { .execute(); } + boolean startExistingRecentsIfPossible(Intent intent, ActivityOptions options) { + final int activityType = mService.getRecentTasks().getRecentsComponent() + .equals(intent.getComponent()) ? ACTIVITY_TYPE_RECENTS : ACTIVITY_TYPE_HOME; + final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea() + .getRootTask(WINDOWING_MODE_UNDEFINED, activityType); + if (rootTask == null) return false; + final ActivityRecord r = rootTask.topRunningActivity(); + if (r == null || r.mVisibleRequested || !r.attachedToProcess() + || !r.mActivityComponent.equals(intent.getComponent()) + // Recents keeps invisible while device is locked. + || r.mDisplayContent.isKeyguardLocked()) { + return false; + } + mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r); + final ActivityMetricsLogger.LaunchingState launchingState = + mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent); + final Task task = r.getTask(); + mService.deferWindowLayout(); + try { + task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT, + 0 /* flags */, task, task /* readyGroupRef */, + options.getRemoteTransition(), null /* displayChange */); + r.mTransitionController.setTransientLaunch(r, + TaskDisplayArea.getRootTaskAbove(rootTask)); + task.moveToFront("startExistingRecents"); + task.mInResumeTopActivity = true; + task.resumeTopActivity(null /* prev */, options, true /* deferPause */); + mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState, + ActivityManager.START_TASK_TO_FRONT, false, r, options); + } finally { + task.mInResumeTopActivity = false; + mService.continueWindowLayout(); + } + return true; + } + void registerRemoteAnimationForNextActivityStart(String packageName, RemoteAnimationAdapter adapter, @Nullable IBinder launchCookie) { mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter, launchCookie); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index eec77898663c..9d8e0877584f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1231,6 +1231,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Nullable String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) { + + final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions); + // A quick path (skip general intent/task resolving) to start recents animation if the + // recents (or home) activity is available in background. + if (opts != null && opts.getOriginalOptions().getTransientLaunch() + && isCallerRecents(Binder.getCallingUid())) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents"); + if (mActivityStartController.startExistingRecentsIfPossible( + intent, opts.getOriginalOptions())) { + return ActivityManager.START_TASK_TO_FRONT; + } + // Else follow the standard launch procedure. + } + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + Binder.restoreCallingIdentity(origId); + } + } + assertPackageMatchesCallingUid(callingPackage); enforceNotIsolatedCaller("startActivityAsUser"); if (Process.isSdkSandboxUid(Binder.getCallingUid())) { @@ -1257,7 +1279,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { .setRequestCode(requestCode) .setStartFlags(startFlags) .setProfilerInfo(profilerInfo) - .setActivityOptions(bOptions) + .setActivityOptions(opts) .setUserId(userId) .execute(); @@ -3521,10 +3543,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mRootWindowContainer.moveActivityToPinnedRootTask(r, null /* launchIntoPipHostActivity */, "enterPictureInPictureMode", transition); - final Task task = r.getTask(); // Continue the pausing process after entering pip. - if (task.getPausingActivity() == r) { - task.schedulePauseActivity(r, false /* userLeaving */, + if (r.isState(PAUSING)) { + r.getTask().schedulePauseActivity(r, false /* userLeaving */, false /* pauseImmediately */, "auto-pip"); } } diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 2e1d3b1643ac..0af046281cc5 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -120,7 +120,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume } else { mTransitionOp = OP_CHANGE; } - } else if (transitionType != WindowManager.TRANSIT_NONE) { + } else if (displayContent.mTransitionController.isShellTransitionsEnabled()) { mTransitionOp = OP_APP_SWITCH; } else { mTransitionOp = OP_LEGACY; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 9d31210c72d2..d9c509c53bb9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -161,6 +161,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; @@ -436,7 +437,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics(); - /** @see #computeCompatSmallestWidth(boolean, int, int) */ + /** @see #computeCompatSmallestWidth(boolean, int, int, int) */ private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); /** @@ -1687,7 +1688,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return false; } } - if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) { + if (r.isState(RESUMED) && !r.getTask().mInResumeTopActivity) { // If the activity is executing or has done the lifecycle callback, use normal // rotation animation so the display info can be updated immediately (see // updateDisplayAndOrientation). This prevents a compatibility issue such as @@ -2012,7 +2013,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // the top of the method, the caller is obligated to call computeNewConfigurationLocked(). // By updating the Display info here it will be available to // #computeScreenConfiguration() later. - updateDisplayAndOrientation(null /* outConfig */); + updateDisplayAndOrientation(getConfiguration().uiMode, null /* outConfig */); // NOTE: We disable the rotation in the emulator because // it doesn't support hardware OpenGL emulation yet. @@ -2062,7 +2063,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * changed. * Do not call if {@link WindowManagerService#mDisplayReady} == false. */ - private DisplayInfo updateDisplayAndOrientation(Configuration outConfig) { + private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) { // Use the effective "visual" dimensions based on current rotation final int rotation = getRotation(); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); @@ -2074,16 +2075,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout(); final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); - final Rect appFrame = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, - wmDisplayCutout); + final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, + displayCutout); + final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, + displayCutout); mDisplayInfo.rotation = rotation; mDisplayInfo.logicalWidth = dw; mDisplayInfo.logicalHeight = dh; mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity; mDisplayInfo.physicalXDpi = mBaseDisplayPhysicalXDpi; mDisplayInfo.physicalYDpi = mBaseDisplayPhysicalYDpi; - mDisplayInfo.appWidth = appFrame.width(); - mDisplayInfo.appHeight = appFrame.height(); + mDisplayInfo.appWidth = appWidth; + mDisplayInfo.appHeight = appHeight; if (isDefaultDisplay) { mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); @@ -2097,7 +2100,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; } - computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, dw, dh, + computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, uiMode, dw, dh, mDisplayMetrics.density, outConfig); mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId, @@ -2187,8 +2190,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp outConfig.windowConfiguration.setMaxBounds(0, 0, dw, dh); outConfig.windowConfiguration.setBounds(outConfig.windowConfiguration.getMaxBounds()); - final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation); - computeScreenAppConfiguration(outConfig, dw, dh, rotation, wmDisplayCutout); + final int uiMode = getConfiguration().uiMode; + final DisplayCutout displayCutout = + calculateDisplayCutoutForRotation(rotation).getDisplayCutout(); + computeScreenAppConfiguration(outConfig, dw, dh, rotation, uiMode, displayCutout); final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); displayInfo.rotation = rotation; @@ -2197,35 +2202,38 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final Rect appBounds = outConfig.windowConfiguration.getAppBounds(); displayInfo.appWidth = appBounds.width(); displayInfo.appHeight = appBounds.height(); - final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout(); displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout; - computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, + computeSizeRangesAndScreenLayout(displayInfo, rotated, uiMode, dw, dh, mDisplayMetrics.density, outConfig); return displayInfo; } /** Compute configuration related to application without changing current display. */ private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh, - int rotation, WmDisplayCutout wmDisplayCutout) { - DisplayFrames displayFrames = - mDisplayPolicy.getSimulatedDisplayFrames(rotation, dw, dh, wmDisplayCutout); - final Rect appFrame = - mDisplayPolicy.getNonDecorDisplayFrameWithSimulatedFrame(displayFrames); + int rotation, int uiMode, DisplayCutout displayCutout) { + final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, + displayCutout); + final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, + displayCutout); + mDisplayPolicy.getNonDecorInsetsLw(rotation, displayCutout, mTmpRect); + final int leftInset = mTmpRect.left; + final int topInset = mTmpRect.top; // AppBounds at the root level should mirror the app screen size. - outConfig.windowConfiguration.setAppBounds(appFrame); + outConfig.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */, + leftInset + appWidth /* right */, topInset + appHeight /* bottom */); 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) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, + uiMode, displayCutout) / density + 0.5f); + outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, + uiMode, displayCutout) / density + 0.5f); outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale); outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dw, dh); + outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw, dh); outConfig.windowConfiguration.setDisplayRotation(rotation); } @@ -2234,7 +2242,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Do not call if mDisplayReady == false. */ void computeScreenConfiguration(Configuration config) { - final DisplayInfo displayInfo = updateDisplayAndOrientation(config); + final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config); final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; mTmpRect.set(0, 0, dw, dh); @@ -2243,8 +2251,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp config.windowConfiguration.setWindowingMode(getWindowingMode()); config.windowConfiguration.setDisplayWindowingMode(getWindowingMode()); - computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, - calculateDisplayCutoutForRotation(getRotation())); + computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, config.uiMode, + displayInfo.displayCutout); config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK) | ((displayInfo.flags & Display.FLAG_ROUND) != 0 @@ -2333,7 +2341,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence); } - private int computeCompatSmallestWidth(boolean rotated, int dw, int dh) { + private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh) { mTmpDisplayMetrics.setTo(mDisplayMetrics); final DisplayMetrics tmpDm = mTmpDisplayMetrics; final int unrotDw, unrotDh; @@ -2344,20 +2352,25 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp unrotDw = dw; unrotDh = dh; } - int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, tmpDm, unrotDw, unrotDh); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, tmpDm, unrotDh, unrotDw); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, tmpDm, unrotDw, unrotDh); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, tmpDm, unrotDh, unrotDw); + int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, + unrotDh); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, + unrotDw); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, + unrotDh); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, + unrotDw); return sw; } - private int reduceCompatConfigWidthSize(int curSize, int rotation, + private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode, DisplayMetrics dm, int dw, int dh) { - final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation); - final Rect nonDecorSize = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, - wmDisplayCutout); - dm.noncompatWidthPixels = nonDecorSize.width(); - dm.noncompatHeightPixels = nonDecorSize.height(); + final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( + rotation).getDisplayCutout(); + dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, + displayCutout); + dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, + displayCutout); float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f); if (curSize == 0 || size < curSize) { @@ -2367,7 +2380,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated, - int dw, int dh, float density, Configuration outConfig) { + int uiMode, int dw, int dh, float density, Configuration outConfig) { // We need to determine the smallest width that will occur under normal // operation. To this, start with the base screen size and compute the @@ -2385,34 +2398,37 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp displayInfo.smallestNominalAppHeight = 1<<30; displayInfo.largestNominalAppWidth = 0; displayInfo.largestNominalAppHeight = 0; - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, uiMode, unrotDw, unrotDh); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, uiMode, unrotDh, unrotDw); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, uiMode, unrotDw, unrotDh); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, uiMode, unrotDh, unrotDw); if (outConfig == null) { return; } int sl = Configuration.resetScreenLayout(outConfig.screenLayout); - sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh); - sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw); - sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh); - sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw); + sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode); + sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode); + sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode); + sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode); outConfig.smallestScreenWidthDp = (int) (displayInfo.smallestNominalAppWidth / density + 0.5f); outConfig.screenLayout = sl; } - private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh) { + private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh, + int uiMode) { // Get the display cutout at this rotation. - final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation); + final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( + rotation).getDisplayCutout(); // Get the app screen size at this rotation. - final Rect size = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, wmDisplayCutout); + int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout); + int h = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, displayCutout); // Compute the screen layout size class for this rotation. - int longSize = size.width(); - int shortSize = size.height(); + int longSize = w; + int shortSize = h; if (longSize < shortSize) { int tmp = longSize; longSize = shortSize; @@ -2423,20 +2439,25 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return Configuration.reduceScreenLayout(curLayout, longSize, shortSize); } - 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; + private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, + int uiMode, int dw, int dh) { + final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( + rotation).getDisplayCutout(); + final int width = mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, + displayCutout); + if (width < displayInfo.smallestNominalAppWidth) { + displayInfo.smallestNominalAppWidth = width; } - if (size.x > displayInfo.largestNominalAppWidth) { - displayInfo.largestNominalAppWidth = size.x; + if (width > displayInfo.largestNominalAppWidth) { + displayInfo.largestNominalAppWidth = width; } - if (size.y < displayInfo.smallestNominalAppHeight) { - displayInfo.smallestNominalAppHeight = size.y; + final int height = mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode, + displayCutout); + if (height < displayInfo.smallestNominalAppHeight) { + displayInfo.smallestNominalAppHeight = height; } - if (size.y > displayInfo.largestNominalAppHeight) { - displayInfo.largestNominalAppHeight = size.y; + if (height > displayInfo.largestNominalAppHeight) { + displayInfo.largestNominalAppHeight = height; } } @@ -6500,12 +6521,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** * Notifies the remote insets controller that the top focused window has changed. * - * @param packageName The name of the package that is open in the top focused window. + * @param component The application component that is open in the top focussed window. * @param requestedVisibilities The insets visibilities requested by the focussed window. */ - void topFocusedWindowChanged(String packageName, InsetsVisibilities requestedVisibilities) { + void topFocusedWindowChanged(ComponentName component, + InsetsVisibilities requestedVisibilities) { try { - mRemoteInsetsController.topFocusedWindowChanged(packageName, requestedVisibilities); + mRemoteInsetsController.topFocusedWindowChanged(component, requestedVisibilities); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver package in top focused window change", e); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 337027947b2c..98a51a97110d 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -102,7 +102,6 @@ import android.content.Intent; 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; @@ -127,8 +126,6 @@ import android.view.InsetsSource; 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; @@ -162,7 +159,6 @@ import com.android.server.policy.WindowManagerPolicy.ScreenOnListener; 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; @@ -1994,6 +1990,35 @@ public class DisplayPolicy { return mUiContext; } + private int getNavigationBarWidth(int rotation, int uiMode, int position) { + if (mNavigationBar == null) { + return 0; + } + LayoutParams lp = mNavigationBar.mAttrs; + if (lp.paramsForRotation != null + && lp.paramsForRotation.length == 4 + && lp.paramsForRotation[rotation] != null) { + lp = lp.paramsForRotation[rotation]; + } + Insets providedInsetsSize = null; + if (lp.providedInsets != null) { + for (InsetsFrameProvider provider : lp.providedInsets) { + if (provider.type != ITYPE_NAVIGATION_BAR) { + continue; + } + providedInsetsSize = provider.insetsSize; + } + } + if (providedInsetsSize != null) { + if (position == NAV_BAR_LEFT) { + return providedInsetsSize.left; + } else if (position == NAV_BAR_RIGHT) { + return providedInsetsSize.right; + } + } + return lp.width; + } + void notifyDisplayReady() { mHandler.post(() -> { final int displayId = getDisplayId(); @@ -2010,24 +2035,45 @@ public class DisplayPolicy { } /** - * 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. + * Return the display width available after excluding any screen + * decorations that could never be removed in Honeycomb. That is, system bar or + * button bar. */ - Rect getNonDecorDisplayFrame(int fullWidth, int fullHeight, int rotation, - WmDisplayCutout cutout) { - final DisplayFrames displayFrames = - getSimulatedDisplayFrames(rotation, fullWidth, fullHeight, cutout); - return getNonDecorDisplayFrameWithSimulatedFrame(displayFrames); + public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, + DisplayCutout displayCutout) { + int width = fullWidth; + if (hasNavigationBar()) { + final int navBarPosition = navigationBarPosition(rotation); + if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) { + width -= getNavigationBarWidth(rotation, uiMode, navBarPosition); + } + } + if (displayCutout != null) { + width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight(); + } + return width; } - Rect getNonDecorDisplayFrameWithSimulatedFrame(DisplayFrames displayFrames) { - final Rect nonDecorInsets = - getInsets(displayFrames, Type.displayCutout() | Type.navigationBars()).toRect(); - final Rect displayFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame()); - displayFrame.inset(nonDecorInsets); - return displayFrame; + @VisibleForTesting + int getNavigationBarHeight(int rotation) { + if (mNavigationBar == null) { + return 0; + } + LayoutParams lp = mNavigationBar.mAttrs.forRotation(rotation); + Insets providedInsetsSize = null; + if (lp.providedInsets != null) { + for (InsetsFrameProvider provider : lp.providedInsets) { + if (provider.type != ITYPE_NAVIGATION_BAR) { + continue; + } + providedInsetsSize = provider.insetsSize; + if (providedInsetsSize != null) { + return providedInsetsSize.bottom; + } + break; + } + } + return lp.height; } /** @@ -2049,24 +2095,53 @@ public class DisplayPolicy { } /** - * Return the available screen size that we should report for the + * Return the display height available after excluding any screen + * decorations that could never be removed in Honeycomb. That is, system bar or + * button bar. + */ + public int getNonDecorDisplayHeight(int fullHeight, int rotation, DisplayCutout displayCutout) { + int height = fullHeight; + final int navBarPosition = navigationBarPosition(rotation); + if (navBarPosition == NAV_BAR_BOTTOM) { + height -= getNavigationBarHeight(rotation); + } + if (displayCutout != null) { + height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom(); + } + return height; + } + + /** + * Return the available screen width that we should report for the * configuration. This must be no larger than - * {@link #getNonDecorDisplayFrame(int, int, int, DisplayCutout)}; it may be smaller + * {@link #getNonDecorDisplayWidth(int, 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); + public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, + DisplayCutout displayCutout) { + return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation, uiMode, displayCutout); } - Point getConfigDisplaySizeWithSimulatedFrame(DisplayFrames displayFrames) { - final Insets insets = getInsets(displayFrames, - Type.displayCutout() | Type.navigationBars() | Type.statusBars()); - Rect configFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame()); - configFrame.inset(insets); - return new Point(configFrame.width(), configFrame.height()); + /** + * Return the available screen height that we should report for the + * configuration. This must be no larger than + * {@link #getNonDecorDisplayHeight(int, int, DisplayCutout)}; it may be smaller + * than that to account for more transient decoration like a status bar. + */ + public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode, + DisplayCutout displayCutout) { + // There is a separate status bar at the top of the display. We don't count that as part + // of the fixed decor, since it can hide; however, for purposes of configurations, + // we do want to exclude it since applications can't generally use that part + // of the screen. + int statusBarHeight = mStatusBarHeightForRotation[rotation]; + if (displayCutout != null) { + // If there is a cutout, it may already have accounted for some part of the status + // bar height. + statusBarHeight = Math.max(0, statusBarHeight - displayCutout.getSafeInsetTop()); + } + return getNonDecorDisplayHeight(fullHeight, rotation, displayCutout) + - statusBarHeight; } /** @@ -2098,69 +2173,48 @@ public class DisplayPolicy { * 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 */ - public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight, - WmDisplayCutout displayCutout, Rect outInsets) { - final DisplayFrames displayFrames = getSimulatedDisplayFrames(displayRotation, - displayWidth, displayHeight, displayCutout); - getStableInsetsWithSimulatedFrame(displayFrames, outInsets); - } + public void getStableInsetsLw(int displayRotation, DisplayCutout displayCutout, + Rect outInsets) { + outInsets.setEmpty(); - void getStableInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) { - // Navigation bar, status bar, and cutout. - outInsets.set(getInsets(displayFrames, - Type.displayCutout() | Type.navigationBars() | Type.statusBars()).toRect()); + // Navigation bar and status bar. + getNonDecorInsetsLw(displayRotation, displayCutout, outInsets); + convertNonDecorInsetsToStableInsets(outInsets, displayRotation); } /** * 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 + * bar or button bar. See {@link #getNonDecorDisplayWidth}. + * @param displayRotation the current display rotation + * @param displayCutout 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(getInsets(displayFrames, - Type.displayCutout() | Type.navigationBars()).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; - } + public void getNonDecorInsetsLw(int displayRotation, DisplayCutout displayCutout, + Rect outInsets) { + outInsets.setEmpty(); + + // Only navigation bar + if (hasNavigationBar()) { + final int uiMode = mService.mPolicy.getUiMode(); + int position = navigationBarPosition(displayRotation); + if (position == NAV_BAR_BOTTOM) { + outInsets.bottom = getNavigationBarHeight(displayRotation); + } else if (position == NAV_BAR_RIGHT) { + outInsets.right = getNavigationBarWidth(displayRotation, uiMode, position); + } else if (position == NAV_BAR_LEFT) { + outInsets.left = getNavigationBarWidth(displayRotation, uiMode, position); + } + } - @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; + if (displayCutout != null) { + outInsets.left += displayCutout.getSafeInsetLeft(); + outInsets.top += displayCutout.getSafeInsetTop(); + outInsets.right += displayCutout.getSafeInsetRight(); + outInsets.bottom += displayCutout.getSafeInsetBottom(); + } } @NavigationBarPosition @@ -2186,7 +2240,7 @@ public class DisplayPolicy { * @see WindowManagerPolicyConstants#NAV_BAR_BOTTOM */ @NavigationBarPosition - int getNavBarPosition() { + public int getNavBarPosition() { return mNavigationBarPosition; } diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index b7ddbd070460..33cdd2e98113 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -265,7 +265,7 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal .setContainerLayer() .setName(name) .setCallsite("createSurfaceForGestureMonitor") - .setParent(dc.getOverlayLayer()) + .setParent(dc.getSurfaceControl()) .build(); } } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 8389dbde3e14..2ce333d5438b 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -64,7 +64,10 @@ import android.view.SurfaceControl; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.protolog.common.ProtoLog; +import com.android.server.LocalServices; +import com.android.server.inputmethod.InputMethodManagerInternal; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -405,8 +408,28 @@ final class InputMonitor { // Shell transitions doesn't use RecentsAnimationController || getWeak(mActiveRecentsActivity) != null && focus.inTransition(); if (shouldApplyRecentsInputConsumer) { - requestFocus(recentsAnimationInputConsumer.mWindowHandle.token, - recentsAnimationInputConsumer.mName); + if (mInputFocus != recentsAnimationInputConsumer.mWindowHandle.token) { + requestFocus(recentsAnimationInputConsumer.mWindowHandle.token, + recentsAnimationInputConsumer.mName); + // Hiding IME/IME icon when recents input consumer gain focus. + if (!mDisplayContent.isImeAttachedToApp()) { + // Hiding IME if IME window is not attached to app since it's not proper to + // snapshot Task with IME window to animate together in this case. + final InputMethodManagerInternal inputMethodManagerInternal = + LocalServices.getService(InputMethodManagerInternal.class); + if (inputMethodManagerInternal != null) { + inputMethodManagerInternal.hideCurrentInputMethod( + SoftInputShowHideReason.HIDE_RECENTS_ANIMATION); + } + } else { + // Disable IME icon explicitly when IME attached to the app in case + // IME icon might flickering while swiping to the next app task still + // in animating before the next app window focused, or IME icon + // persists on the bottom when swiping the task to recents. + InputMethodManagerInternal.get().updateImeWindowStatus( + true /* disableImeIcon */); + } + } return; } } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 620a56d18dcd..3e2d7c928936 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -46,6 +46,7 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.StatusBarManager; import android.app.WindowConfiguration; +import android.content.ComponentName; import android.content.res.Resources; import android.graphics.Rect; import android.util.ArrayMap; @@ -548,8 +549,10 @@ class InsetsPolicy { return focusedWin; } if (remoteInsetsControllerControlsSystemBars(focusedWin)) { + ComponentName component = focusedWin.mActivityRecord != null + ? focusedWin.mActivityRecord.mActivityComponent : null; mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged( - focusedWin.mAttrs.packageName, focusedWin.getRequestedVisibilities()); + component, focusedWin.getRequestedVisibilities()); return mDisplayContent.mRemoteInsetsControlTarget; } if (mPolicy.areSystemBarsForcedShownLw()) { @@ -606,8 +609,10 @@ class InsetsPolicy { return null; } if (remoteInsetsControllerControlsSystemBars(focusedWin)) { + ComponentName component = focusedWin.mActivityRecord != null + ? focusedWin.mActivityRecord.mActivityComponent : null; mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged( - focusedWin.mAttrs.packageName, focusedWin.getRequestedVisibilities()); + component, focusedWin.getRequestedVisibilities()); return mDisplayContent.mRemoteInsetsControlTarget; } if (mPolicy.areSystemBarsForcedShownLw()) { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index ca4376e0f263..d76f6be93aeb 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -170,11 +170,12 @@ class KeyguardController { final KeyguardDisplayState state = getDisplayState(displayId); final boolean aodChanged = aodShowing != state.mAodShowing; + final boolean aodRemoved = state.mAodShowing && !aodShowing; // If keyguard is going away, but SystemUI aborted the transition, need to reset state. - // Do not reset keyguardChanged status if this is aodChanged. + // Do not reset keyguardChanged status when only AOD is removed. final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing) - || (state.mKeyguardGoingAway && keyguardShowing && !aodChanged); - if (aodChanged && !aodShowing) { + || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved); + if (aodRemoved) { updateDeferWakeTransition(false /* waiting */); } if (!keyguardChanged && !aodChanged) { diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index e8a63d453004..53f1fe6abec5 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -70,7 +70,6 @@ import android.window.PictureInPictureSurfaceTransaction; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledLambda; @@ -326,26 +325,6 @@ public class RecentsAnimationController implements DeathRecipient { } } InputMethodManagerInternal.get().maybeFinishStylusHandwriting(); - if (!behindSystemBars) { - // Hiding IME if IME window is not attached to app. - // Since some windowing mode is not proper to snapshot Task with IME window - // while the app transitioning to the next task (e.g. split-screen mode) - if (!mDisplayContent.isImeAttachedToApp()) { - final InputMethodManagerInternal inputMethodManagerInternal = - LocalServices.getService(InputMethodManagerInternal.class); - if (inputMethodManagerInternal != null) { - inputMethodManagerInternal.hideCurrentInputMethod( - SoftInputShowHideReason.HIDE_RECENTS_ANIMATION); - } - } else { - // Disable IME icon explicitly when IME attached to the app in case - // IME icon might flickering while swiping to the next app task still - // in animating before the next app window focused, or IME icon - // persists on the bottom when swiping the task to recents. - InputMethodManagerInternal.get().updateImeWindowStatus( - true /* disableImeIcon */); - } - } mService.mWindowPlacerLocked.requestTraversal(); } } finally { @@ -373,10 +352,6 @@ public class RecentsAnimationController implements DeathRecipient { } } - // TODO(b/166736352): Remove this method without the need to expose to launcher. - @Override - public void hideCurrentInputMethod() { } - @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { synchronized (mService.mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index baa31a073dd2..2879e33fb71a 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.Manifest.permission.CONTROL_KEYGUARD; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; +import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.Manifest.permission.STATUS_BAR_SERVICE; import static android.app.ActivityTaskManager.INVALID_TASK_ID; @@ -247,6 +248,14 @@ public class SafeActivityOptions { throw new SecurityException(msg); } } + if (options.getTransientLaunch() && !supervisor.mRecentTasks.isCallerRecents(callingUid) + && ActivityTaskManagerService.checkPermission( + MANAGE_ACTIVITY_TASKS, callingPid, callingUid) == PERMISSION_DENIED) { + final String msg = "Permission Denial: starting transient launch from " + callerApp + + ", pid=" + callingPid + ", uid=" + callingUid; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } // Check if the caller is allowed to launch on the specified display area. final WindowContainerToken daToken = options.getLaunchTaskDisplayArea(); final TaskDisplayArea taskDisplayArea = daToken != null diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index f80e732c8212..8cad16509c4c 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -173,11 +173,6 @@ class ScreenRotationAnimation { if (isSizeChanged) { mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays(); - } else { - // Exclude rounded corner overlay from screenshot buffer. Rounded - // corner overlay windows are un-rotated during rotation animation - // for a seamless transition. - builder.setExcludeLayers(displayContent.findRoundedCornerOverlays()); } SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = @@ -600,7 +595,7 @@ class ScreenRotationAnimation { } private SurfaceAnimator startDisplayRotation() { - return startAnimation(initializeBuilder() + SurfaceAnimator animator = startAnimation(initializeBuilder() .setAnimationLeashParent(mDisplayContent.getSurfaceControl()) .setSurfaceControl(mDisplayContent.getWindowingLayer()) .setParentSurfaceControl(mDisplayContent.getSurfaceControl()) @@ -609,6 +604,13 @@ class ScreenRotationAnimation { .build(), createWindowAnimationSpec(mRotateEnterAnimation), this::onAnimationEnd); + + // Crop the animation leash to avoid extended wallpaper from showing over + // mBackColorSurface + Rect displayBounds = mDisplayContent.getBounds(); + mDisplayContent.getPendingTransaction() + .setWindowCrop(animator.mLeash, displayBounds.width(), displayBounds.height()); + return animator; } private SurfaceAnimator startScreenshotAlphaAnimation() { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index b34a3ede889c..21c5886f085b 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.PooledLambda; 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; @@ -2195,13 +2194,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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); + policy.getNonDecorInsetsLw(displayInfo.rotation, + displayInfo.displayCutout, mTmpInsets); intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets); - policy.getStableInsetsWithSimulatedFrame(displayFrames, mTmpInsets); + + policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation); intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c455ac1dd370..91f69a55d400 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -46,6 +46,7 @@ import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; +import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -84,11 +85,9 @@ import android.window.TransitionInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; -import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.server.LocalServices; import com.android.server.inputmethod.InputMethodManagerInternal; import java.lang.annotation.Retention; @@ -958,26 +957,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } - // Hiding IME/IME icon when starting quick-step with resents animation. - if (!mTargetDisplays.get(mRecentsDisplayId).isImeAttachedToApp()) { - // Hiding IME if IME window is not attached to app. - // Since some windowing mode is not proper to snapshot Task with IME window - // while the app transitioning to the next task (e.g. split-screen mode) - final InputMethodManagerInternal inputMethodManagerInternal = - LocalServices.getService(InputMethodManagerInternal.class); - if (inputMethodManagerInternal != null) { - inputMethodManagerInternal.hideCurrentInputMethod( - SoftInputShowHideReason.HIDE_RECENTS_ANIMATION); - } - } else { - // Disable IME icon explicitly when IME attached to the app in case - // IME icon might flickering while swiping to the next app task still - // in animating before the next app window focused, or IME icon - // persists on the bottom when swiping the task to recents. - InputMethodManagerInternal.get().updateImeWindowStatus( - true /* disableImeIcon */); - } - // The rest of this function handles nav-bar reparenting if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() @@ -1761,6 +1740,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (occludesKeyguard(wc)) { flags |= FLAG_OCCLUDES_KEYGUARD; } + if (wc.isEmbedded()) { + flags |= FLAG_IS_EMBEDDED; + } return flags; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 86876c327f7b..9d6e250e8a47 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -317,7 +317,6 @@ import com.android.server.policy.WindowManagerPolicy; 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; @@ -1854,8 +1853,7 @@ public class WindowManagerService extends IWindowManager.Stub ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s" + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5)); - if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation()) - || win.providesNonDecorInsets()) { + if (win.isVisibleRequestedOrAdding() && displayContent.updateOrientation()) { displayContent.sendNewConfiguration(); } @@ -6378,8 +6376,7 @@ public class WindowManagerService extends IWindowManager.Stub + " callers=" + Debug.getCallers(3)); return NAV_BAR_INVALID; } - return displayContent.getDisplayPolicy().navigationBarPosition( - displayContent.getDisplayRotation().getRotation()); + return displayContent.getDisplayPolicy().getNavBarPosition(); } } @@ -7220,9 +7217,7 @@ public class WindowManagerService extends IWindowManager.Stub 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); + dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.displayCutout, outInsets); } } @@ -8267,7 +8262,7 @@ public class WindowManagerService extends IWindowManager.Stub .setContainerLayer() .setName("IME Handwriting Surface") .setCallsite("getHandwritingSurfaceForDisplay") - .setParent(dc.getOverlayLayer()) + .setParent(dc.getSurfaceControl()) .build(); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b3fe19f41233..af8c4c8e9370 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -244,7 +244,6 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewTreeObserver; import android.view.WindowInfo; -import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager; import android.view.animation.Animation; @@ -1922,19 +1921,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return (mPolicyVisibility & POLICY_VISIBILITY_ALL) == POLICY_VISIBILITY_ALL; } - boolean providesNonDecorInsets() { - if (mProvidedInsetsSources == null) { - return false; - } - for (int i = mProvidedInsetsSources.size() - 1; i >= 0; i--) { - final int type = mProvidedInsetsSources.keyAt(i); - if ((InsetsState.toPublicType(type) & WindowInsets.Type.navigationBars()) != 0) { - return true; - } - } - return false; - } - void clearPolicyVisibilityFlag(int policyVisibilityFlag) { mPolicyVisibility &= ~policyVisibilityFlag; mWmService.scheduleAnimationLocked(); @@ -2645,19 +2631,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } removeImmediately(); - boolean sentNewConfig = false; + // Removing a visible window will effect the computed orientation + // So just update orientation if needed. 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(); - } mWmService.updateFocusedWindowLocked(isFocused() ? UPDATE_FOCUS_REMOVING_FOCUS : UPDATE_FOCUS_NORMAL, diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 437c9344d793..bbb21f8122c5 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -362,7 +362,7 @@ class WindowToken extends WindowContainer<WindowState> { @Override void assignLayer(SurfaceControl.Transaction t, int layer) { if (mRoundedCornerOverlay) { - super.assignLayer(t, WindowManagerPolicy.SCREEN_DECOR_DISPLAY_OVERLAY_LAYER); + super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1); } else { super.assignLayer(t, layer); } @@ -372,7 +372,7 @@ class WindowToken extends WindowContainer<WindowState> { SurfaceControl.Builder makeSurface() { final SurfaceControl.Builder builder = super.makeSurface(); if (mRoundedCornerOverlay) { - builder.setParent(getDisplayContent().getOverlayLayer()); + builder.setParent(null); } return builder; } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 494246491e47..67ef7f5bded8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -722,6 +722,25 @@ public class AlarmManagerServiceTest { } @Test + public void testAlarmBroadcastOption() throws Exception { + final long triggerTime = mNowElapsedTest + 5000; + final PendingIntent alarmPi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi); + + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + + final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor = + ArgumentCaptor.forClass(PendingIntent.OnFinished.class); + final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), + onFinishedCaptor.capture(), any(Handler.class), isNull(), + optionsCaptor.capture()); + assertTrue(optionsCaptor.getValue() + .getBoolean(BroadcastOptions.KEY_ALARM_BROADCAST, false)); + } + + @Test public void testUpdateConstants() { setDeviceConfigLong(KEY_MIN_FUTURITY, 5); setDeviceConfigLong(KEY_MIN_INTERVAL, 10); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java index ece0a627f051..b0738fdb78d0 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -17,7 +17,11 @@ package com.android.server.display; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; @@ -45,18 +49,21 @@ public class LogicalDisplayTest { private LogicalDisplay mLogicalDisplay; private DisplayDevice mDisplayDevice; + private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo(); @Before public void setUp() { // Share classloader to allow package private access. System.setProperty("dexmaker.share_classloader", "true"); mDisplayDevice = mock(DisplayDevice.class); - DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); - displayDeviceInfo.width = DISPLAY_WIDTH; - displayDeviceInfo.height = DISPLAY_HEIGHT; - displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); - when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo); + + mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo()); + mDisplayDeviceInfo.width = DISPLAY_WIDTH; + mDisplayDeviceInfo.height = DISPLAY_HEIGHT; + mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; + mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; + when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo); // Disable binder caches in this process. PropertyInvalidatedCache.disableForTestMode(); @@ -103,4 +110,33 @@ public class LogicalDisplayTest { mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); } + + @Test + public void testDisplayInputFlags() { + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); + reset(t); + + mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_NONE; + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + verify(t).setDisplayFlags(any(), eq(0)); + reset(t); + + mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL; + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); + reset(t); + + mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_DISABLED); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + verify(t).setDisplayFlags(any(), eq(0)); + reset(t); + + mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_ENABLED); + mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); + reset(t); + } } 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 ea0e8c91c930..3f3d01a14f80 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -157,7 +157,6 @@ import androidx.test.filters.MediumTest; 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; @@ -551,8 +550,7 @@ public class ActivityRecordTests extends WindowTestsBase { 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.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.displayCutout, insets); policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation); Task.intersectWithInsetsIfFits(stableRect, stableRect, insets); @@ -593,8 +591,7 @@ public class ActivityRecordTests extends WindowTestsBase { 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.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.displayCutout, insets); policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation); Task.intersectWithInsetsIfFits(stableRect, stableRect, insets); 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 a001eda2f86e..f41fee789bf2 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 static org.hamcrest.Matchers.equalTo; 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 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @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 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @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 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @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 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @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 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @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 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @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 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @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,28 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @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, - int right, int bottom) { - mErrorCollector.checkThat("stableInsets", getStableInsetsLw(diPair.first, diPair.second), - equalTo(new Rect(left, top, right, bottom))); + private void verifyStableInsets(DisplayInfo di, int left, int top, int right, int bottom) { + mErrorCollector.checkThat("stableInsets", getStableInsetsLw(di), equalTo(new Rect( + left, top, right, bottom))); } - private void verifyNonDecorInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top, - int right, int bottom) { - mErrorCollector.checkThat("nonDecorInsets", - getNonDecorInsetsLw(diPair.first, diPair.second), equalTo(new Rect( + private void verifyNonDecorInsets(DisplayInfo di, int left, int top, int right, int bottom) { + mErrorCollector.checkThat("nonDecorInsets", getNonDecorInsetsLw(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) { + verifyConsistency("configDisplay", di, getStableInsetsLw(di), + getConfigDisplayWidth(di), getConfigDisplayHeight(di)); + verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di), + getNonDecorDisplayWidth(di), getNonDecorDisplayHeight(di)); } private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width, @@ -181,42 +165,39 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { equalTo(di.logicalHeight - insets.top - insets.bottom)); } - private Rect getStableInsetsLw(DisplayInfo di, WmDisplayCutout cutout) { + private Rect getStableInsetsLw(DisplayInfo di) { Rect result = new Rect(); - mDisplayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, - cutout, result); + mDisplayPolicy.getStableInsetsLw(di.rotation, di.displayCutout, result); return result; } - private Rect getNonDecorInsetsLw(DisplayInfo di, WmDisplayCutout cutout) { + private Rect getNonDecorInsetsLw(DisplayInfo di) { Rect result = new Rect(); - mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, - cutout, result); + mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.displayCutout, result); return result; } - private int getNonDecorDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) { - return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight, - di.rotation, cutout).width(); + private int getNonDecorDisplayWidth(DisplayInfo di) { + return mDisplayPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight, + di.rotation, 0 /* ui */, di.displayCutout); } - private int getNonDecorDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) { - return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight, - di.rotation, cutout).height(); + private int getNonDecorDisplayHeight(DisplayInfo di) { + return mDisplayPolicy.getNonDecorDisplayHeight(di.logicalHeight, di.rotation, + di.displayCutout); } - private int getConfigDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) { - return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight, - di.rotation, cutout).x; + private int getConfigDisplayWidth(DisplayInfo di) { + return mDisplayPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight, + di.rotation, 0 /* ui */, di.displayCutout); } - private int getConfigDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) { - return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight, - di.rotation, cutout).y; + private int getConfigDisplayHeight(DisplayInfo di) { + return mDisplayPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight, + di.rotation, 0 /* ui */, di.displayCutout); } - private static Pair<DisplayInfo, WmDisplayCutout> displayInfoForRotation(int rotation, - boolean withDisplayCutout) { - return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false); + private static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) { + return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false).first; } } 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 e2fe1b175dc8..1f03039de72b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -268,6 +268,20 @@ public class TaskTests extends WindowTestsBase { assertFalse(task.hasChild()); // In real case, the task should be preserved for adding new activity. assertTrue(task.isAttached()); + + final ActivityRecord activityA = new ActivityBuilder(mAtm).setTask(task).build(); + final ActivityRecord activityB = new ActivityBuilder(mAtm).setTask(task).build(); + final ActivityRecord activityC = new ActivityBuilder(mAtm).setTask(task).build(); + activityA.setState(ActivityRecord.State.STOPPED, "test"); + activityB.setState(ActivityRecord.State.PAUSED, "test"); + activityC.setState(ActivityRecord.State.RESUMED, "test"); + doReturn(true).when(activityB).shouldBeVisibleUnchecked(); + doReturn(true).when(activityC).shouldBeVisibleUnchecked(); + activityA.getConfiguration().densityDpi += 100; + assertTrue(task.performClearTop(activityA, 0 /* launchFlags */).finishing); + // The bottom activity should destroy directly without relaunch for config change. + assertEquals(ActivityRecord.State.DESTROYING, activityA.getState()); + verify(activityA, never()).startRelaunching(); } @Test 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 e0de76f95fee..1e64e469fe7f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -26,9 +26,12 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; + import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; @@ -40,7 +43,6 @@ import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; -import android.view.WindowInsets; import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; @@ -202,11 +204,7 @@ class TestDisplayContent extends DisplayContent { 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())); + doReturn(20).when(displayPolicy).getNavigationBarHeight(anyInt()); } else { doReturn(false).when(displayPolicy).hasNavigationBar(); doReturn(false).when(displayPolicy).hasStatusBar(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index e1fbf96b2e71..c323e02ad149 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -30,6 +30,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -61,8 +62,10 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.view.SurfaceControl; import android.window.IDisplayAreaOrganizer; +import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.ITransitionPlayer; +import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; import androidx.test.filters.SmallTest; @@ -1005,6 +1008,43 @@ public class TransitionTests extends WindowTestsBase { assertTrue(openTransition.allReady()); } + @Test + public void testIsEmbeddedChange() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + final ArraySet<WindowContainer> participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + mAtm.mTaskFragmentOrganizerController.registerOrganizer( + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder())); + final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(2) + .setOrganizer(organizer) + .build(); + final ActivityRecord closingActivity = embeddedTf.getBottomMostActivity(); + final ActivityRecord openingActivity = embeddedTf.getTopMostActivity(); + // Start states. + changes.put(embeddedTf, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + // End states. + closingActivity.mVisibleRequested = false; + openingActivity.mVisibleRequested = true; + + participants.add(closingActivity); + participants.add(openingActivity); + final ArrayList<WindowContainer> targets = Transition.calculateTargets( + participants, changes); + final TransitionInfo info = Transition.calculateTransitionInfo( + transition.mType, 0 /* flags */, targets, changes, mMockT); + + assertEquals(2, info.getChanges().size()); + assertTrue((info.getChanges().get(0).getFlags() & FLAG_IS_EMBEDDED) != 0); + assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0); + } + private static void makeTaskOrganized(Task... tasks) { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); for (Task t : tasks) { 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 919418bde821..0cbf1b2c7cc8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -323,10 +323,6 @@ class WindowTestsBase extends SystemServiceTestsBase { mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM; mNavBarWindow.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4]; mNavBarWindow.mAttrs.setFitInsetsTypes(0); - mNavBarWindow.mAttrs.layoutInDisplayCutoutMode = - LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mNavBarWindow.mAttrs.privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { mNavBarWindow.mAttrs.paramsForRotation[rot] = getNavBarLayoutParamsForRotation(rot); @@ -383,9 +379,6 @@ class WindowTestsBase extends SystemServiceTestsBase { lp.height = height; lp.gravity = gravity; lp.setFitInsetsTypes(0); - lp.privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; - lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; return lp; } @@ -818,7 +811,7 @@ class WindowTestsBase extends SystemServiceTestsBase { } @Override - public void topFocusedWindowChanged(String packageName, + public void topFocusedWindowChanged(ComponentName component, InsetsVisibilities requestedVisibilities) { } }; diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 4924a82c385f..423022599de6 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -1146,4 +1146,35 @@ public final class SmsApplication { } return null; } + + /** + * Check if a package is default mms app (or equivalent, like bluetooth) + * + * @param context context from the calling app + * @param packageName the name of the package to be checked + * @return true if the package is default mms app or bluetooth + */ + @UnsupportedAppUsage + public static boolean isDefaultMmsApplication(Context context, String packageName) { + if (packageName == null) { + return false; + } + String defaultMmsPackage = getDefaultMmsApplicationPackageName(context); + String bluetoothPackageName = context.getResources() + .getString(com.android.internal.R.string.config_systemBluetoothStack); + + if ((defaultMmsPackage != null && defaultMmsPackage.equals(packageName)) + || bluetoothPackageName.equals(packageName)) { + return true; + } + return false; + } + + private static String getDefaultMmsApplicationPackageName(Context context) { + ComponentName component = getDefaultMmsApplication(context, false); + if (component != null) { + return component.getPackageName(); + } + return null; + } } diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index 38becc6af0dc..297940e9c8c6 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -49,7 +49,7 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa private static final String TAG = "CellSignalStrengthNr"; // Lifted from Default carrier configs and max range of SSRSRP - // Boundaries: [-140 dB, -44 dB] + // Boundaries: [-156 dB, -31 dB] private int[] mSsRsrpThresholds = new int[] { -110, /* SIGNAL_STRENGTH_POOR */ -90, /* SIGNAL_STRENGTH_MODERATE */ @@ -173,14 +173,14 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa */ public CellSignalStrengthNr(int csiRsrp, int csiRsrq, int csiSinr, int csiCqiTableIndex, List<Byte> csiCqiReport, int ssRsrp, int ssRsrq, int ssSinr) { - mCsiRsrp = inRangeOrUnavailable(csiRsrp, -140, -44); + mCsiRsrp = inRangeOrUnavailable(csiRsrp, -156, -31); mCsiRsrq = inRangeOrUnavailable(csiRsrq, -20, -3); mCsiSinr = inRangeOrUnavailable(csiSinr, -23, 23); mCsiCqiTableIndex = inRangeOrUnavailable(csiCqiTableIndex, 1, 3); mCsiCqiReport = csiCqiReport.stream() - .map(cqi -> new Integer(inRangeOrUnavailable(Byte.toUnsignedInt(cqi), 0, 15))) + .map(cqi -> inRangeOrUnavailable(Byte.toUnsignedInt(cqi), 0, 15)) .collect(Collectors.toList()); - mSsRsrp = inRangeOrUnavailable(ssRsrp, -140, -44); + mSsRsrp = inRangeOrUnavailable(ssRsrp, -156, -31); mSsRsrq = inRangeOrUnavailable(ssRsrq, -43, 20); mSsSinr = inRangeOrUnavailable(ssSinr, -23, 40); updateLevel(null, null); @@ -212,8 +212,8 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa } /** - * Reference: 3GPP TS 38.215. - * Range: -140 dBm to -44 dBm. + * Reference: 3GPP TS 38.133 10.1.6.1. + * Range: -156 dBm to -31 dBm. * @return SS reference signal received power, {@link CellInfo#UNAVAILABLE} means unreported * value. */ @@ -242,8 +242,8 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa } /** - * Reference: 3GPP TS 38.215. - * Range: -140 dBm to -44 dBm. + * Reference: 3GPP TS 38.133 10.1.6.1. + * Range: -156 dBm to -31 dBm. * @return CSI reference signal received power, {@link CellInfo#UNAVAILABLE} means unreported * value. */ diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java index 06c5b5cfeda1..5e02532e85a8 100644 --- a/telephony/java/android/telephony/UiccSlotInfo.java +++ b/telephony/java/android/telephony/UiccSlotInfo.java @@ -129,7 +129,7 @@ public class UiccSlotInfo implements Parcelable { this.mLogicalSlotIdx = logicalSlotIdx; this.mIsExtendedApduSupported = isExtendedApduSupported; this.mIsRemovable = false; - this.mPortList = null; + this.mPortList = new ArrayList<UiccPortInfo>(); } /** |