diff options
188 files changed, 4189 insertions, 2689 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/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/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/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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8ec32a6d6b5b..7be8dffdf023 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3759,6 +3759,7 @@ public final class ViewRootImpl implements ViewParent, } } mFirstInputStage.onWindowFocusChanged(hasWindowFocus); + mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus); // NOTE: there's no view visibility (appeared / disapparead) events when the windows focus // is lost, so we don't need to to force a flush - there might be other events such as 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/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java index 5924844aa3b2..f1a052b61c59 100644 --- a/core/java/android/window/ImeOnBackInvokedDispatcher.java +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -81,8 +81,10 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc @OnBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback) { final Bundle bundle = new Bundle(); + // Always invoke back for ime without checking the window focus. final IOnBackInvokedCallback iCallback = - new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback); + new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback, + () -> true); bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder()); bundle.putInt(RESULT_KEY_PRIORITY, priority); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index d147524d3b3d..02c5ebcc184e 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; import java.util.TreeMap; +import java.util.function.Supplier; /** * Provides window based implementation of {@link OnBackInvokedDispatcher}. @@ -64,6 +65,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> mOnBackInvokedCallbacks = new TreeMap<>(); private final Checker mChecker; + private boolean mHasFocus; public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) { mChecker = new Checker(applicationCallBackEnabled); @@ -189,7 +191,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { .ImeOnBackInvokedCallback ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) callback).getIOnBackInvokedCallback() - : new OnBackInvokedCallbackWrapper(callback); + : new OnBackInvokedCallbackWrapper(callback, this::hasFocus); callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority); } mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo); @@ -198,6 +200,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } + /** + * Called when window focus changed. + */ + public void onWindowFocusChanged(boolean hasFocus) { + mHasFocus = hasFocus; + } + + private boolean hasFocus() { + return mHasFocus; + } + public OnBackInvokedCallback getTopCallback() { if (mAllCallbacks.isEmpty()) { return null; @@ -221,9 +234,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { private final WeakReference<OnBackInvokedCallback> mCallback; - - OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) { + private final Supplier<Boolean> mHasFocus; + OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback, + @NonNull Supplier<Boolean> hasFocus) { mCallback = new WeakReference<>(callback); + mHasFocus = hasFocus; } @Override @@ -263,7 +278,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { if (callback == null) { return; } - + if (!mHasFocus.get()) { + Log.w(TAG, "Skip back invoke due to current focus has lost."); + return; + } callback.onBackInvoked(); }); } diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml index 2e7b62ceb723..3c7a5c578fc5 100644 --- a/core/res/res/layout-car/car_alert_dialog.xml +++ b/core/res/res/layout-car/car_alert_dialog.xml @@ -54,7 +54,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/text_view_start_margin" android:layout_marginEnd="@dimen/text_view_end_margin" - style="@style/CarBody4"/> + style="@style/CarDialogMessageText"/> <!-- we don't need this spacer, but the id needs to be here for compatibility --> <Space diff --git a/core/res/res/layout-car/car_alert_dialog_button_bar.xml b/core/res/res/layout-car/car_alert_dialog_button_bar.xml index 4f815b887088..43fd1eb4c25f 100644 --- a/core/res/res/layout-car/car_alert_dialog_button_bar.xml +++ b/core/res/res/layout-car/car_alert_dialog_button_bar.xml @@ -16,13 +16,13 @@ --> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/buttonPanel" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:scrollbarAlwaysDrawVerticalTrack="true" - android:scrollIndicators="top|bottom" - android:fillViewport="true" - style="?attr/buttonBarStyle"> + android:id="@+id/buttonPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scrollbarAlwaysDrawVerticalTrack="true" + android:scrollIndicators="top|bottom" + android:fillViewport="true" + style="?attr/buttonBarStyle"> <com.android.internal.widget.ButtonBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -35,7 +35,7 @@ <Button android:id="@+id/button3" - style="@style/CarAction1" + style="@style/CarDialogButtonText" android:minWidth="@dimen/car_touch_target_size" android:paddingStart="@dimen/car_padding_2" android:paddingEnd="@dimen/car_padding_2" @@ -46,7 +46,7 @@ <Button android:id="@+id/button2" - style="@style/CarAction1" + style="@style/CarDialogButtonText" android:minWidth="@dimen/car_touch_target_size" android:paddingStart="@dimen/car_padding_2" android:paddingEnd="@dimen/car_padding_2" @@ -57,7 +57,7 @@ <Button android:id="@+id/button1" - style="@style/CarAction1" + style="@style/CarDialogButtonText" android:minWidth="@dimen/car_touch_target_size" android:paddingStart="@dimen/car_padding_2" android:paddingEnd="@dimen/car_padding_2" diff --git a/core/res/res/values/styles_car.xml b/core/res/res/values/styles_car.xml index ca3ba936630e..0655fde70934 100644 --- a/core/res/res/values/styles_car.xml +++ b/core/res/res/values/styles_car.xml @@ -14,96 +14,15 @@ limitations under the License. --> <resources> - <!-- Car text --> - <style name="CarBody1"> - <item name="textStyle">normal</item> - <item name="textSize">@dimen/car_body1_size</item> - <item name="textColor">@color/car_body1</item> - </style> - - <style name="CarBody1.Light"> - <item name="textColor">@color/car_body1_light</item> - </style> - - <style name="CarBody1.Dark"> - <item name="textColor">@color/car_body2_dark</item> - </style> - - <style name="CarBody2"> - <item name="textStyle">normal</item> - <item name="textSize">@dimen/car_body2_size</item> - <item name="textColor">@color/car_body2</item> - </style> - - <style name="CarBody2.Dark"> - <item name="textColor">@color/car_body2_dark</item> - </style> - <style name="CarBody2.Light"> - <item name="textColor">@color/car_body2_light</item> - </style> - - <style name="CarBody3"> - <item name="textStyle">normal</item> - <item name="textSize">@dimen/car_body3_size</item> - <item name="textColor">@color/car_body3</item> - </style> - - <!-- The smallest styling for body text. The color of this text changes based on the day/night - mode. --> - <style name="CarBody4"> + <!-- The Dialog message text style--> + <style name="CarDialogMessageText"> <item name="textStyle">normal</item> <item name="textSize">@dimen/car_body4_size</item> <item name="textColor">@color/car_body4</item> </style> - - <style name="CarAction1"> - <item name="textStyle">bold</item> - <item name="textSize">@dimen/car_action1_size</item> - <item name="textColor">@color/control_default_material</item> - </style> - - <style name="CarAction1.Dark"> - <item name="textColor">@color/car_highlight_dark</item> - </style> - <style name="CarAction1.Light"> - <item name="textColor">@color/car_highlight_light</item> - </style> - - <!-- The styling for title text. The color of this text changes based on day/night mode. --> - <style name="CarTitle" > - <item name="textStyle">bold</item> - <item name="textSize">@dimen/car_title2_size</item> - <item name="textColor">@color/car_title</item> - </style> - - <!-- Title text that is permanently a dark color. --> - <style name="CarTitle.Dark" > - <item name="textColor">@color/car_title_dark</item> - </style> - - <!-- Title text that is permanently a light color. --> - <style name="CarTitle.Light" > - <item name="textColor">@color/car_title_light</item> - </style> - - <!-- Action bar --> - <style name="ActionBarTitle" parent="@style/Widget.DeviceDefault.TextView"> - <item name="android:singleLine">true</item> - <item name="android:textAppearance">?attr/textAppearanceLarge</item> - </style> - - <style name="ActionBarButton" - parent="@style/Widget.DeviceDefault.Button.Borderless.Colored"> - <item name="android:textAppearance">@style/ActionBarButtonTextAppearance</item> - <!-- Button's internal horizontal padding --> - <item name="android:paddingStart">@*android:dimen/car_padding_3</item> - <item name="android:paddingEnd">@*android:dimen/car_padding_3</item> - <item name="android:drawablePadding">@*android:dimen/car_padding_2</item> - <item name="android:maxWidth">@*android:dimen/action_bar_button_max_width</item> - </style> - - <style name="ActionBarButtonTextAppearance" - parent="@style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored"> - <item name="android:textAllCaps">false</item> + <!-- This style makes Dialog button text use medium font weight. --> + <style name="CarDialogButtonText"> + <item name="android:textAppearance">@style/TextAppearance.DeviceDefault.Widget.Button</item> + <item name="android:textColor">@color/control_default_material</item> </style> </resources> 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/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index f448cb3091e7..b5194f637395 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -66,6 +66,7 @@ public class WindowOnBackInvokedDispatcherTest { MockitoAnnotations.initMocks(this); mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */); mDispatcher.attachToWindow(mWindowSession, mWindow); + mDispatcher.onWindowFocusChanged(true); } private void waitForIdle() { @@ -152,4 +153,31 @@ public class WindowOnBackInvokedDispatcherTest { waitForIdle(); verify(mCallback2).onBackStarted(); } + + @Test + public void skipBackInvokeWhenNoFocus() throws RemoteException { + ArgumentCaptor<OnBackInvokedCallbackInfo> captor = + ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); + + mDispatcher.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); + + verify(mWindowSession, times(1)).setOnBackInvokedCallbackInfo( + Mockito.eq(mWindow), + captor.capture()); + + verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture()); + + // Should invoke back if it's still in focused. + captor.getValue().getCallback().onBackInvoked(); + waitForIdle(); + verify(mCallback1).onBackInvoked(); + + // In case the focus has lost during back gesture. + mDispatcher.onWindowFocusChanged(false); + + captor.getValue().getCallback().onBackInvoked(); + waitForIdle(); + verifyZeroInteractions(mCallback1); + } } diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 84e949a18c52..f8c015f28a50 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -932,7 +932,7 @@ <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font> </family> <family lang="und-Laoo" variant="compact"> - <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf + <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf </font> <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font> </family> 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/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/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/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/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index b69cbfa83d7c..40cf9a32d7a5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -727,31 +727,23 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) { if (offsetX == 0 && offsetY == 0) { wct.setBounds(taskInfo1.token, mBounds1); - wct.setAppBounds(taskInfo1.token, null); wct.setScreenSizeDp(taskInfo1.token, SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); wct.setBounds(taskInfo2.token, mBounds2); - wct.setAppBounds(taskInfo2.token, null); wct.setScreenSizeDp(taskInfo2.token, SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); } else { - mTempRect.set(taskInfo1.configuration.windowConfiguration.getBounds()); + getBounds1(mTempRect); mTempRect.offset(offsetX, offsetY); wct.setBounds(taskInfo1.token, mTempRect); - mTempRect.set(taskInfo1.configuration.windowConfiguration.getAppBounds()); - mTempRect.offset(offsetX, offsetY); - wct.setAppBounds(taskInfo1.token, mTempRect); wct.setScreenSizeDp(taskInfo1.token, taskInfo1.configuration.screenWidthDp, taskInfo1.configuration.screenHeightDp); - mTempRect.set(taskInfo2.configuration.windowConfiguration.getBounds()); + getBounds2(mTempRect); mTempRect.offset(offsetX, offsetY); wct.setBounds(taskInfo2.token, mTempRect); - mTempRect.set(taskInfo2.configuration.windowConfiguration.getAppBounds()); - mTempRect.offset(offsetX, offsetY); - wct.setAppBounds(taskInfo2.token, mTempRect); wct.setScreenSizeDp(taskInfo2.token, taskInfo2.configuration.screenWidthDp, taskInfo2.configuration.screenHeightDp); 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/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index ed5e24776bfa..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 @@ -57,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; @@ -85,8 +82,6 @@ import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInterface; -import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; -import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; @@ -173,12 +168,6 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) { - return Optional.of(dragAndDropController.asDragAndDrop()); - } - - @WMSingleton - @Provides static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, Context context, CompatUIController compatUI, @@ -207,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 @@ -375,13 +360,6 @@ 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, ShellController shellController, DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) { @@ -416,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) // 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 fb51473a4d98..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 @@ -225,6 +225,7 @@ public abstract class WMShellModule { @Provides @DynamicOverride static SplitScreenController provideSplitScreenController( + ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @@ -234,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); 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 0d0961c41a87..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 @@ -81,11 +81,9 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange 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. @@ -107,11 +105,6 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange mLogger = new DragAndDropEventLogger(uiEventLogger); mIconProvider = iconProvider; mMainExecutor = mainExecutor; - mImpl = new DragAndDropImpl(); - } - - public DragAndDrop asDragAndDrop() { - return mImpl; } public void initialize(Optional<SplitScreenController> splitscreen) { @@ -353,8 +346,4 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange dragLayout = dl; } } - - private class DragAndDropImpl implements DragAndDrop { - // TODO: To be removed - } } 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 b091ab8c692c..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 @@ -40,8 +40,6 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener 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; @@ -62,23 +60,18 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer(context, displayController, mainExecutor); - return new HideDisplayCutoutController(context, shellController, organizer, mainExecutor); + return new HideDisplayCutoutController(context, shellController, organizer); } HideDisplayCutoutController(Context context, ShellController shellController, - HideDisplayCutoutOrganizer organizer, ShellExecutor mainExecutor) { + HideDisplayCutoutOrganizer organizer) { mContext = context; mShellController = shellController; mOrganizer = organizer; - mMainExecutor = mainExecutor; updateStatus(); mShellController.addConfigurationChangeListener(this); } - public HideDisplayCutout asHideDisplayCutout() { - return mImpl; - } - @VisibleForTesting void updateStatus() { // The config value is used for controlling enabling/disabling status of the feature and is @@ -112,8 +105,4 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener pw.println(mEnabled); mOrganizer.dump(pw); } - - private class HideDisplayCutoutImpl implements HideDisplayCutout { - // TODO: To be removed - } } 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 ee99f327f742..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 @@ -85,9 +85,4 @@ public interface OneHanded { * 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 ea2d50851dcc..24f02ac1a6cf 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 @@ -55,6 +55,7 @@ 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; @@ -63,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, ConfigurationChangeListener { + DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener, + KeyguardChangeListener { private static final String TAG = "OneHandedController"; private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = @@ -279,6 +281,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mState.addSListeners(mTutorialHandler); mShellController.addConfigurationChangeListener(this); + mShellController.addKeyguardChangeListener(this); } public OneHanded asOneHanded() { @@ -605,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) { @@ -675,9 +681,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, return; } + if (mState.getState() == STATE_ACTIVE) { + mOneHandedUiEventLogger.writeEvent( + OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT); + } + mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct); - mOneHandedUiEventLogger.writeEvent( - OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT); } /** @@ -756,13 +765,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, 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 2ac112977ece..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 @@ -101,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 5ae460268635..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 @@ -88,6 +88,7 @@ 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; @@ -102,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>, ConfigurationChangeListener { + RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener { private static final String TAG = "PipController"; private Context mContext; @@ -527,6 +528,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); mShellController.addConfigurationChangeListener(this); + mShellController.addKeyguardChangeListener(this); } @Override @@ -661,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); @@ -996,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/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 21bea4674805..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 @@ -80,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; @@ -99,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> { + RemoteCallable<SplitScreenController>, KeyguardChangeListener { private static final String TAG = SplitScreenController.class.getSimpleName(); public static final int EXIT_REASON_UNKNOWN = 0; @@ -127,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; @@ -147,7 +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; - public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer, + public SplitScreenController(ShellController shellController, + ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellExecutor mainExecutor, DisplayController displayController, @@ -155,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; @@ -185,6 +190,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } public void onOrganizerRegistered() { + mShellController.addKeyguardChangeListener(this); if (mStageCoordinator == null) { // TODO: Multi-display mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, @@ -283,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() { @@ -658,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/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 index 28a30f4905a5..837acecef56f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -47,7 +47,9 @@ public class ShellController { private final ShellExecutor mMainExecutor; private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl(); - private final CopyOnWriteArrayList<ConfigurationChangeListener> mListeners = + private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners = + new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners = new CopyOnWriteArrayList<>(); private Configuration mLastConfiguration; @@ -68,15 +70,31 @@ public class ShellController { * particular order. */ public void addConfigurationChangeListener(ConfigurationChangeListener listener) { - mListeners.remove(listener); - mListeners.add(listener); + mConfigChangeListeners.remove(listener); + mConfigChangeListeners.add(listener); } /** * Removes an existing configuration listener. */ public void removeConfigurationChangeListener(ConfigurationChangeListener listener) { - mListeners.remove(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 @@ -102,7 +120,7 @@ public class ShellController { // Update the last configuration and call listeners mLastConfiguration.updateFrom(newConfig); - for (ConfigurationChangeListener listener : mListeners) { + for (ConfigurationChangeListener listener : mConfigChangeListeners) { listener.onConfigurationChanged(newConfig); if (densityFontScaleChanged) { listener.onDensityOrFontScaleChanged(); @@ -119,11 +137,26 @@ public class ShellController { } } + @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 + "mListeners=" + mListeners.size()); + pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size()); pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration); + pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size()); } /** @@ -136,5 +169,19 @@ public class ShellController { 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 index a2d072cd7d88..a15ce5d2b816 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java @@ -30,4 +30,16 @@ 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/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/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java index 68e955e7cf7a..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 @@ -29,7 +29,6 @@ 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; @@ -49,8 +48,6 @@ public class HideDisplayCutoutControllerTest extends ShellTestCase { private ShellController mShellController; @Mock private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer; - @Mock - private ShellExecutor mMockMainExecutor; private HideDisplayCutoutController mHideDisplayCutoutController; @@ -58,7 +55,7 @@ public class HideDisplayCutoutControllerTest extends ShellTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mHideDisplayCutoutController = new HideDisplayCutoutController( - mContext, mShellController, mMockDisplayAreaOrganizer, mMockMainExecutor); + mContext, mShellController, mMockDisplayAreaOrganizer); } @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 958969deb564..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 @@ -149,6 +149,11 @@ public class OneHandedControllerTest extends OneHandedTestCase { } @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/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 827785bcc3e0..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 @@ -123,6 +123,11 @@ public class PipControllerTest extends ShellTestCase { } @Test + public void instantiatePipController_registerKeyguardChangeListener() { + verify(mMockShellController, times(1)).addKeyguardChangeListener(any()); + } + + @Test public void instantiatePipController_registersPipTransitionCallback() { verify(mMockPipTransitionController).registerPipTransitionCallback(any()); } 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 c10e4a143076..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 @@ -28,6 +28,9 @@ 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; @@ -44,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; @@ -65,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; @@ -82,10 +88,17 @@ 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 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 index 6cc3ae8001e4..1c0e46f7264e 100644 --- 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 @@ -46,12 +46,14 @@ public class ShellControllerTest extends ShellTestCase { private ShellExecutor mExecutor; private ShellController mController; - private TestConfigurationChangeListener mListener; + private TestConfigurationChangeListener mConfigChangeListener; + private TestKeyguardChangeListener mKeyguardChangeListener; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mListener = new TestConfigurationChangeListener(); + mKeyguardChangeListener = new TestKeyguardChangeListener(); + mConfigChangeListener = new TestConfigurationChangeListener(); mController = new ShellController(mExecutor); mController.onConfigurationChanged(getConfigurationCopy()); } @@ -62,47 +64,97 @@ public class ShellControllerTest extends ShellTestCase { } @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(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); newConfig.densityDpi = 200; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); + assertTrue(mConfigChangeListener.configChanges == 1); } @Test public void testDoubleAddConfigurationChangeListener_ensureSingleCallback() { - mController.addConfigurationChangeListener(mListener); - mController.addConfigurationChangeListener(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); + mController.addConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); newConfig.densityDpi = 200; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); + assertTrue(mConfigChangeListener.configChanges == 1); } @Test public void testAddRemoveConfigurationChangeListener_ensureNoCallback() { - mController.addConfigurationChangeListener(mListener); - mController.removeConfigurationChangeListener(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); + mController.removeConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); newConfig.densityDpi = 200; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 0); + assertTrue(mConfigChangeListener.configChanges == 0); } @Test public void testMultipleConfigurationChangeListeners() { TestConfigurationChangeListener listener2 = new TestConfigurationChangeListener(); - mController.addConfigurationChangeListener(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); mController.addConfigurationChangeListener(listener2); Configuration newConfig = getConfigurationCopy(); newConfig.densityDpi = 200; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); + assertTrue(mConfigChangeListener.configChanges == 1); assertTrue(listener2.configChanges == 1); } @@ -115,7 +167,7 @@ public class ShellControllerTest extends ShellTestCase { } }; mController.addConfigurationChangeListener(badListener); - mController.addConfigurationChangeListener(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); // Ensure we don't fail just because a listener was removed mid-callback Configuration newConfig = getConfigurationCopy(); @@ -125,77 +177,77 @@ public class ShellControllerTest extends ShellTestCase { @Test public void testDensityChangeCallback() { - mController.addConfigurationChangeListener(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); newConfig.densityDpi = 200; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); - assertTrue(mListener.densityChanges == 1); - assertTrue(mListener.smallestWidthChanges == 0); - assertTrue(mListener.themeChanges == 0); - assertTrue(mListener.localeChanges == 0); + 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(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); newConfig.fontScale = 2; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); - assertTrue(mListener.densityChanges == 1); - assertTrue(mListener.smallestWidthChanges == 0); - assertTrue(mListener.themeChanges == 0); - assertTrue(mListener.localeChanges == 0); + 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(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); newConfig.smallestScreenWidthDp = 100; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); - assertTrue(mListener.densityChanges == 0); - assertTrue(mListener.smallestWidthChanges == 1); - assertTrue(mListener.themeChanges == 0); - assertTrue(mListener.localeChanges == 0); + 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(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); newConfig.assetsSeq++; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); - assertTrue(mListener.densityChanges == 0); - assertTrue(mListener.smallestWidthChanges == 0); - assertTrue(mListener.themeChanges == 1); - assertTrue(mListener.localeChanges == 0); + 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(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); newConfig.uiMode = Configuration.UI_MODE_NIGHT_YES; mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); - assertTrue(mListener.densityChanges == 0); - assertTrue(mListener.smallestWidthChanges == 0); - assertTrue(mListener.themeChanges == 1); - assertTrue(mListener.localeChanges == 0); + 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(mListener); + mController.addConfigurationChangeListener(mConfigChangeListener); Configuration newConfig = getConfigurationCopy(); // Just change the locales to be different @@ -205,11 +257,11 @@ public class ShellControllerTest extends ShellTestCase { newConfig.locale = Locale.CANADA; } mController.onConfigurationChanged(newConfig); - assertTrue(mListener.configChanges == 1); - assertTrue(mListener.densityChanges == 0); - assertTrue(mListener.smallestWidthChanges == 0); - assertTrue(mListener.themeChanges == 0); - assertTrue(mListener.localeChanges == 1); + assertTrue(mConfigChangeListener.configChanges == 1); + assertTrue(mConfigChangeListener.densityChanges == 0); + assertTrue(mConfigChangeListener.smallestWidthChanges == 0); + assertTrue(mConfigChangeListener.themeChanges == 0); + assertTrue(mConfigChangeListener.localeChanges == 1); } private Configuration getConfigurationCopy() { @@ -253,4 +305,27 @@ public class ShellControllerTest extends ShellTestCase { 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/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml index 50f69d1d692a..c629d96bcf4b 100644 --- a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml +++ b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml @@ -71,6 +71,8 @@ <TextView android:id="@+id/entity_header_second_summary" style="@style/TextAppearance.EntityHeaderSummary" + android:singleLine="false" + android:maxLines="4" android:layout_width="wrap_content" android:layout_height="wrap_content"/> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f92cc8eda268..4f84c8c817cb 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -246,9 +246,9 @@ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] --> <string name="bluetooth_profile_pan">Internet access</string> <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] --> - <string name="bluetooth_profile_pbap">Contact sharing</string> + <string name="bluetooth_profile_pbap">Contacts and call history sharing</string> <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] --> - <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string> + <string name="bluetooth_profile_pbap_summary">Use for contacts and call history sharing</string> <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] --> <string name="bluetooth_profile_pan_nap">Internet connection sharing</string> <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. --> 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/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index dbdbdf6ee2ba..2f36ab9aa93d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -515,11 +515,20 @@ private class AnimatedDialog( dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE) // Make sure the dialog is visible instantly and does not do any window animation. - window.attributes.windowAnimations = R.style.Animation_LaunchAnimation + val attributes = window.attributes + attributes.windowAnimations = R.style.Animation_LaunchAnimation // Ensure that the animation is not clipped by the display cut-out when animating this // dialog into an app. - window.attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + + // Ensure that the animation is not clipped by the navigation/task bars when animating this + // dialog into an app. + val wasFittingNavigationBars = + attributes.fitInsetsTypes and WindowInsets.Type.navigationBars() != 0 + attributes.fitInsetsTypes = + attributes.fitInsetsTypes and WindowInsets.Type.navigationBars().inv() + window.attributes = window.attributes // We apply the insets ourselves to make sure that the paddings are set on the correct @@ -527,7 +536,13 @@ private class AnimatedDialog( window.setDecorFitsSystemWindows(false) val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup) viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets -> - val insets = windowInsets.getInsets(WindowInsets.Type.displayCutout()) + val type = if (wasFittingNavigationBars) { + WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars() + } else { + WindowInsets.Type.displayCutout() + } + + val insets = windowInsets.getInsets(type) view.setPadding(insets.left, insets.top, insets.right, insets.bottom) WindowInsets.CONSUMED } 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/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/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/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/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index b4646aee444c..a8a526a33229 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt @@ -29,6 +29,7 @@ interface Flag<T> { interface ParcelableFlag<T> : Flag<T>, Parcelable { val default: T + val overridden: Boolean override fun describeContents() = 0 } @@ -52,7 +53,8 @@ interface SysPropFlag<T> : Flag<T> { data class BooleanFlag @JvmOverloads constructor( override val id: Int, override val default: Boolean = false, - override val teamfood: Boolean = false + override val teamfood: Boolean = false, + override val overridden: Boolean = false ) : ParcelableFlag<Boolean> { companion object { @@ -65,12 +67,16 @@ data class BooleanFlag @JvmOverloads constructor( private constructor(parcel: Parcel) : this( id = parcel.readInt(), - default = parcel.readBoolean() + default = parcel.readBoolean(), + teamfood = parcel.readBoolean(), + overridden = parcel.readBoolean() ) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeInt(id) parcel.writeBoolean(default) + parcel.writeBoolean(teamfood) + parcel.writeBoolean(overridden) } } @@ -100,7 +106,8 @@ data class SysPropBooleanFlag @JvmOverloads constructor( data class StringFlag @JvmOverloads constructor( override val id: Int, override val default: String = "", - override val teamfood: Boolean = false + override val teamfood: Boolean = false, + override val overridden: Boolean = false ) : ParcelableFlag<String> { companion object { @JvmField @@ -130,7 +137,8 @@ data class ResourceStringFlag @JvmOverloads constructor( data class IntFlag @JvmOverloads constructor( override val id: Int, override val default: Int = 0, - override val teamfood: Boolean = false + override val teamfood: Boolean = false, + override val overridden: Boolean = false ) : ParcelableFlag<Int> { companion object { @@ -161,7 +169,8 @@ data class ResourceIntFlag @JvmOverloads constructor( data class LongFlag @JvmOverloads constructor( override val id: Int, override val default: Long = 0, - override val teamfood: Boolean = false + override val teamfood: Boolean = false, + override val overridden: Boolean = false ) : ParcelableFlag<Long> { companion object { @@ -186,7 +195,8 @@ data class LongFlag @JvmOverloads constructor( data class FloatFlag @JvmOverloads constructor( override val id: Int, override val default: Float = 0f, - override val teamfood: Boolean = false + override val teamfood: Boolean = false, + override val overridden: Boolean = false ) : ParcelableFlag<Float> { companion object { @@ -217,7 +227,8 @@ data class ResourceFloatFlag @JvmOverloads constructor( data class DoubleFlag @JvmOverloads constructor( override val id: Int, override val default: Double = 0.0, - override val teamfood: Boolean = false + override val teamfood: Boolean = false, + override val overridden: Boolean = false ) : ParcelableFlag<Double> { companion object { @@ -237,4 +248,4 @@ data class DoubleFlag @JvmOverloads constructor( parcel.writeInt(id) parcel.writeDouble(default) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt index 26e40e1ecad3..d172690e2488 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt @@ -64,20 +64,29 @@ class FlagManager constructor( intent.setPackage(RECEIVING_PACKAGE) return CallbackToFutureAdapter.getFuture { - completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> -> - context.sendOrderedBroadcast(intent, null, - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val extras: Bundle? = getResultExtras(false) - val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? = - extras?.getParcelableArrayList(EXTRA_FLAGS) - if (listOfFlags != null) { - completer.set(listOfFlags) - } else { - completer.setException(NoFlagResultsException()) - } + completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> -> + context.sendOrderedBroadcast( + intent, + null, + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras: Bundle? = getResultExtras(false) + val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? = + extras?.getParcelableArrayList( + EXTRA_FLAGS, ParcelableFlag::class.java + ) + if (listOfFlags != null) { + completer.set(listOfFlags) + } else { + completer.setException(NoFlagResultsException()) } - }, null, Activity.RESULT_OK, "extra data", null) + } + }, + null, + Activity.RESULT_OK, + "extra data", + null + ) "QueryingFlags" } } @@ -152,7 +161,11 @@ class FlagManager constructor( } val parts = uri.pathSegments val idStr = parts[parts.size - 1] - val id = try { idStr.toInt() } catch (e: NumberFormatException) { return } + val id = try { + idStr.toInt() + } catch (e: NumberFormatException) { + return + } clearCacheAction?.accept(id) dispatchListenersAndMaybeRestart(id, onSettingsChangedAction) } @@ -188,4 +201,5 @@ class FlagManager constructor( } class NoFlagResultsException : Exception( - "SystemUI failed to communicate its flags back successfully")
\ No newline at end of file + "SystemUI failed to communicate its flags back successfully" +) 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/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/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 08096b03263d..24fcf15d7a11 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -96,16 +96,12 @@ public abstract class SystemUIInitializer { .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 @@ -116,16 +112,12 @@ public abstract class SystemUIInitializer { .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 adeafd5fa347..fd7680f463c0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -41,17 +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.sysui.ShellInterface; -import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; import com.android.wm.shell.transition.ShellTransitions; import java.util.Map; @@ -100,9 +96,6 @@ public interface SysUIComponent { Builder setTaskViewFactory(Optional<TaskViewFactory> t); @BindsInstance - Builder setHideDisplayCutout(Optional<HideDisplayCutout> h); - - @BindsInstance Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump); @BindsInstance @@ -115,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 0e0073a1690f..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; @@ -124,6 +125,7 @@ import dagger.Provides; LogModule.class, MediaProjectionModule.class, PeopleHubModule.class, + PeopleModule.class, PluginModule.class, PrivacyModule.class, QsFrameTranslateModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index b6003e91eeba..e4c0325d4d5a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -29,20 +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.sysui.ShellInterface; -import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; import com.android.wm.shell.transition.ShellTransitions; import java.util.Optional; @@ -104,9 +100,6 @@ public interface WMComponent { Optional<Bubbles> getBubbles(); @WMSingleton - Optional<HideDisplayCutout> getHideDisplayCutout(); - - @WMSingleton Optional<TaskViewFactory> getTaskViewFactory(); @WMSingleton @@ -119,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/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/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index 49b3908558d8..c5221cd9641b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -32,7 +32,6 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.DeviceConfig; import android.util.Log; import androidx.annotation.NonNull; @@ -197,7 +196,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { /** Specific override for Boolean flags that checks against the teamfood list.*/ private boolean readFlagValue(int id, boolean defaultValue) { - Boolean result = readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE); + Boolean result = readBooleanFlagOverride(id); // Only check for teamfood if the default is false. if (!defaultValue && result == null && id != Flags.TEAMFOOD.getId()) { if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) { @@ -208,6 +207,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { return result == null ? defaultValue : result; } + private Boolean readBooleanFlagOverride(int id) { + return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE); + } + @NonNull private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) { requireNonNull(defaultValue, "defaultValue"); @@ -407,11 +410,18 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { @Nullable private ParcelableFlag<?> toParcelableFlag(Flag<?> f) { if (f instanceof BooleanFlag) { - return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f), f.getTeamfood()); + return new BooleanFlag( + f.getId(), + isEnabled((BooleanFlag) f), + f.getTeamfood(), + readBooleanFlagOverride(f.getId()) != null); } if (f instanceof ResourceBooleanFlag) { return new BooleanFlag( - f.getId(), isEnabled((ResourceBooleanFlag) f), f.getTeamfood()); + f.getId(), + isEnabled((ResourceBooleanFlag) f), + f.getTeamfood(), + readBooleanFlagOverride(f.getId()) != null); } if (f instanceof DeviceConfigBooleanFlag) { return new BooleanFlag( @@ -420,7 +430,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { if (f instanceof SysPropBooleanFlag) { // TODO(b/223379190): Teamfood not supported for sysprop flags yet. return new BooleanFlag( - f.getId(), isEnabled((SysPropBooleanFlag) f), false); + f.getId(), + ((SysPropBooleanFlag) f).getDefault(), + false, + !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty()); } // TODO: add support for other flag types. diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 7ad4a06579f7..a566984748a7 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -145,6 +145,9 @@ public class Flags { public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER = new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip); + public static final BooleanFlag STATUS_BAR_LETTERBOX_APPEARANCE = + new BooleanFlag(603, false); + /***************************************/ // 700 - dialer/calls public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP = 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/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/hidedisplaycutout/HideDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt index dd1c8d6d49e1..5d8539fabc6b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.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,13 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.hidedisplaycutout; +package com.android.systemui.people.data.model -import com.android.wm.shell.common.annotations.ExternalThread; +import android.graphics.drawable.Icon +import com.android.systemui.people.widget.PeopleTileKey -/** - * Interface to engage hide display cutout feature. - */ -@ExternalThread -public interface HideDisplayCutout { -} +/** 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/draganddrop/DragAndDrop.java b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt index 03351871caad..40205ce9424a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.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,13 +14,14 @@ * limitations under the License. */ -package com.android.wm.shell.draganddrop; +package com.android.systemui.people.ui.viewmodel -import com.android.wm.shell.common.annotations.ExternalThread; +import android.graphics.Bitmap +import com.android.systemui.people.widget.PeopleTileKey -/** - * Interface for telling DragAndDrop stuff. - */ -@ExternalThread -public interface DragAndDrop { -} +/** 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/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/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt index 1c4911de1d45..58acfb40ee44 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt @@ -7,6 +7,8 @@ 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.panelstate.PanelExpansionChangeEvent import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager @@ -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/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/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/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 4956a27698bc..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 @@ -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/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index fff7b6a9e4f0..2d6d846bf2a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1709,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) { @@ -2457,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. @@ -2535,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 = @@ -2551,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; @@ -4083,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/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/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/StatusBarBoundsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt new file mode 100644 index 000000000000..f5ba399fe51a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt @@ -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.systemui.statusbar.phone + +import android.graphics.Rect +import android.view.View +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.END_SIDE_CONTENT +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.START_SIDE_CONTENT +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope +import com.android.systemui.util.boundsOnScreen +import javax.inject.Inject +import javax.inject.Named + +/** Provides various bounds within the status bar. */ +@StatusBarFragmentScope +class StatusBarBoundsProvider +@Inject +constructor( + private val changeListeners: Set<@JvmSuppressWildcards BoundsChangeListener>, + @Named(START_SIDE_CONTENT) private val startSideContent: View, + @Named(END_SIDE_CONTENT) private val endSideContent: View, +) : StatusBarFragmentComponent.Startable { + + interface BoundsChangeListener { + fun onStatusBarBoundsChanged() + } + + private var previousBounds = + BoundsPair(start = startSideContent.boundsOnScreen, end = endSideContent.boundsOnScreen) + + private val layoutListener = + View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + val newBounds = BoundsPair(start = visibleStartSideBounds, end = visibleEndSideBounds) + if (previousBounds != newBounds) { + previousBounds = newBounds + changeListeners.forEach { it.onStatusBarBoundsChanged() } + } + } + + override fun start() { + startSideContent.addOnLayoutChangeListener(layoutListener) + endSideContent.addOnLayoutChangeListener(layoutListener) + } + + override fun stop() { + startSideContent.removeOnLayoutChangeListener(layoutListener) + endSideContent.removeOnLayoutChangeListener(layoutListener) + } + + /** + * Returns the bounds of the end side of the status bar that are visible to the user. The end + * side is right when in LTR and is left when in RTL. + * + * Note that even though the layout might be larger, here we only return the bounds for what is + * visible to the user. + * + * The end side of the status bar contains the multi-user switcher and status icons such as + * wi-fi, battery, etc + */ + val visibleEndSideBounds: Rect + get() = endSideContent.boundsOnScreen + + /** + * Returns the bounds of the start side of the status bar that are visible to the user. The + * start side is left when in LTR and is right when in RTL. + * + * Note that even though the layout might be larger, here we only return the bounds for what is + * visible to the user. + * + * The start side of the status bar contains the operator name, clock, on-going call chip, and + * notifications. + */ + val visibleStartSideBounds: Rect + get() = startSideContent.boundsOnScreen +} + +private data class BoundsPair(val start: Rect, val end: Rect) 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/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 41df8e3cbb31..88e985f9eeaf 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 @@ -30,6 +30,7 @@ import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.biometrics.AuthRippleView; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -269,7 +270,8 @@ public abstract class StatusBarViewModule { CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, OperatorNameViewController.Factory operatorNameViewControllerFactory, SecureSettings secureSettings, - @Main Executor mainExecutor + @Main Executor mainExecutor, + DumpManager dumpManager ) { return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory, ongoingCallController, @@ -289,7 +291,8 @@ public abstract class StatusBarViewModule { collapsedStatusBarFragmentLogger, operatorNameViewControllerFactory, secureSettings, - mainExecutor); + mainExecutor, + dumpManager); } /** 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 0e7da81f3b27..0848729781bd 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 @@ -34,6 +34,8 @@ import android.os.Parcelable; import android.os.UserHandle; import android.provider.Settings; import android.telephony.SubscriptionManager; +import android.util.ArrayMap; +import android.util.IndentingPrintWriter; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; @@ -43,9 +45,11 @@ import android.widget.LinearLayout; import androidx.annotation.VisibleForTesting; +import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; @@ -66,6 +70,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; @@ -76,9 +81,12 @@ import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListen import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener; import com.android.systemui.util.settings.SecureSettings; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; /** @@ -89,7 +97,7 @@ import java.util.concurrent.Executor; @SuppressLint("ValidFragment") public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks, StatusBarStateController.StateListener, - SystemStatusAnimationCallback { + SystemStatusAnimationCallback, Dumpable { public static final String TAG = "CollapsedStatusBarFragment"; private static final String EXTRA_PANEL_STATE = "panel_state"; @@ -102,7 +110,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; @@ -124,8 +132,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final SecureSettings mSecureSettings; private final Executor mMainExecutor; + private final DumpManager mDumpManager; private List<String> mBlockedIcons = new ArrayList<>(); + private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>(); private SignalCallback mSignalCallback = new SignalCallback() { @Override @@ -185,7 +195,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, OperatorNameViewController.Factory operatorNameViewControllerFactory, SecureSettings secureSettings, - @Main Executor mainExecutor + @Main Executor mainExecutor, + DumpManager dumpManager ) { mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory; mOngoingCallController = ongoingCallController; @@ -206,6 +217,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mOperatorNameViewControllerFactory = operatorNameViewControllerFactory; mSecureSettings = secureSettings; mMainExecutor = mainExecutor; + mDumpManager = dumpManager; } @Override @@ -217,8 +229,15 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + mDumpManager.registerDumpable(getClass().getSimpleName(), this); mStatusBarFragmentComponent = mStatusBarFragmentComponentFactory.create(this); mStatusBarFragmentComponent.init(); + mStartableStates.clear(); + for (Startable startable : mStatusBarFragmentComponent.getStartables()) { + mStartableStates.put(startable, Startable.State.STARTING); + startable.start(); + mStartableStates.put(startable, Startable.State.STARTED); + } mStatusBar = (PhoneStatusBarView) view; View contents = mStatusBar.findViewById(R.id.status_bar_contents); @@ -232,16 +251,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); } @@ -321,6 +340,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } mCarrierConfigTracker.removeCallback(mCarrierConfigCallback); mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener); + + for (Startable startable : mStatusBarFragmentComponent.getStartables()) { + mStartableStates.put(startable, Startable.State.STOPPING); + startable.stop(); + mStartableStates.put(startable, Startable.State.STOPPED); + } + mDumpManager.unregisterDumpable(getClass().getSimpleName()); } /** Initializes views related to the notification icon area. */ @@ -370,10 +396,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 +500,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); } } @@ -670,4 +696,23 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue updateStatusBarLocation(left, right); } }; + + @Override + public void dump(PrintWriter printWriter, String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, /* singleIndent= */" "); + StatusBarFragmentComponent component = mStatusBarFragmentComponent; + if (component == null) { + pw.println("StatusBarFragmentComponent is null"); + } else { + Set<Startable> startables = component.getStartables(); + pw.println("Startables: " + startables.size()); + pw.increaseIndent(); + for (Startable startable : startables) { + Startable.State startableState = mStartableStates.getOrDefault(startable, + Startable.State.NONE); + pw.println(startable + ", state: " + startableState); + } + pw.decreaseIndent(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java index 6717bc768fbb..d9a58442d856 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java @@ -23,9 +23,12 @@ import com.android.systemui.statusbar.phone.LightsOutNotifController; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; +import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.StatusBarDemoMode; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; +import java.util.Set; + import dagger.BindsInstance; import dagger.Subcomponent; @@ -41,7 +44,10 @@ import dagger.Subcomponent; * should be included here or in {@link StatusBarFragmentModule}. */ -@Subcomponent(modules = {StatusBarFragmentModule.class}) +@Subcomponent(modules = { + StatusBarFragmentModule.class, + StatusBarStartablesModule.class +}) @StatusBarFragmentScope public interface StatusBarFragmentComponent { /** Simple factory. */ @@ -52,6 +58,18 @@ public interface StatusBarFragmentComponent { } /** + * Performs initialization logic after {@link StatusBarFragmentComponent} has been constructed. + */ + interface Startable { + void start(); + void stop(); + + enum State { + NONE, STARTING, STARTED, STOPPING, STOPPED + } + } + + /** * Initialize anything extra for the component. Must be called after the component is created. */ default void init() { @@ -92,4 +110,10 @@ public interface StatusBarFragmentComponent { /** */ @StatusBarFragmentScope PhoneStatusBarTransitions getPhoneStatusBarTransitions(); + + /** */ + Set<Startable> getStartables(); + + /** */ + StatusBarBoundsProvider getBoundsProvider(); } 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 7252dfb76c14..41f1f9589ce4 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 @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; +import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController; @@ -34,12 +35,14 @@ import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; import java.util.Optional; +import java.util.Set; import javax.inject.Named; import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.Multibinds; /** Dagger module for {@link StatusBarFragmentComponent}. */ @Module @@ -48,6 +51,8 @@ public interface StatusBarFragmentModule { String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view"; String OPERATOR_NAME_VIEW = "operator_name_view"; String OPERATOR_NAME_FRAME_VIEW = "operator_name_frame_view"; + String START_SIDE_CONTENT = "start_side_content"; + String END_SIDE_CONTENT = "end_side_content"; /** */ @Provides @@ -68,6 +73,22 @@ public interface StatusBarFragmentModule { /** */ @Provides @StatusBarFragmentScope + @Named(START_SIDE_CONTENT) + static View startSideContent(@RootView PhoneStatusBarView view) { + return view.findViewById(R.id.status_bar_start_side_content); + } + + /** */ + @Provides + @StatusBarFragmentScope + @Named(END_SIDE_CONTENT) + static View endSideContent(@RootView PhoneStatusBarView view) { + return view.findViewById(R.id.status_bar_end_side_content); + } + + /** */ + @Provides + @StatusBarFragmentScope @Named(LIGHTS_OUT_NOTIF_VIEW) static View provideLightsOutNotifView(@RootView PhoneStatusBarView view) { return view.findViewById(R.id.notification_lights_out); @@ -138,4 +159,8 @@ public interface StatusBarFragmentModule { static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) { return view.findViewById(R.id.heads_up_status_bar_view); } + + /** */ + @Multibinds + Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt new file mode 100644 index 000000000000..9003d13df0a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.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.statusbar.phone.fragment.dagger + +import com.android.systemui.statusbar.phone.StatusBarBoundsProvider +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +internal interface StatusBarStartablesModule { + + @Binds + @IntoSet + fun statusBarBoundsCalculator( + statusBarBoundsProvider: StatusBarBoundsProvider + ): StatusBarFragmentComponent.Startable +} diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt index 57b3f53c48fe..ec7aabb58b49 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt +++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt @@ -16,7 +16,9 @@ package com.android.systemui.util +import android.graphics.Rect import android.util.IndentingPrintWriter +import android.view.View import android.view.ViewGroup import java.io.PrintWriter @@ -45,4 +47,12 @@ inline fun PrintWriter.indentIfPossible(block: PrintWriter.() -> Unit) { if (this is IndentingPrintWriter) increaseIndent() block() if (this is IndentingPrintWriter) decreaseIndent() -}
\ No newline at end of file +} + +/** Convenience extension property for [View.getBoundsOnScreen]. */ +val View.boundsOnScreen: Rect + get() { + val bounds = Rect() + getBoundsOnScreen(bounds) + return bounds + } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index e08a9d022fb9..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; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index fa2af56743c6..83b0022b9f53 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -55,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; @@ -110,10 +107,7 @@ public final class WMShell extends CoreStartable 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; @@ -127,10 +121,7 @@ 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 @@ -139,10 +130,7 @@ public final class WMShell extends CoreStartable 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, @@ -164,12 +152,9 @@ public final class WMShell extends CoreStartable mPipOptional = pipOptional; mSplitScreenOptional = splitScreenOptional; mOneHandedOptional = oneHandedOptional; - mHideDisplayCutoutOptional = hideDisplayCutoutOptional; mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; mShellCommandHandler = shellCommandHandler; - mCompatUIOptional = sizeCompatUIOptional; - mDragAndDropOptional = dragAndDropOptional; mUserInfoController = userInfoController; mSysUiMainExecutor = sysUiMainExecutor; } @@ -185,6 +170,22 @@ public final class WMShell extends CoreStartable } }); + // 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); @@ -192,7 +193,6 @@ public final class WMShell extends CoreStartable mPipOptional.ifPresent(this::initPip); mSplitScreenOptional.ifPresent(this::initSplitScreen); mOneHandedOptional.ifPresent(this::initOneHanded); - mCompatUIOptional.ifPresent(this::initCompatUi); } @VisibleForTesting @@ -204,20 +204,6 @@ 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); @@ -230,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() { @@ -283,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); } @@ -340,17 +313,6 @@ public final class WMShell extends CoreStartable }); } - @VisibleForTesting - void initCompatUi(CompatUI sizeCompatUI) { - mCompatUIKeyguardCallback = new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - sizeCompatUI.onKeyguardShowingChanged(mKeyguardStateController.isShowing()); - } - }; - mKeyguardStateController.addCallback(mCompatUIKeyguardCallback); - } - @Override public void writeToProto(SystemUiTraceProto proto) { if (proto.wmShell == null) { 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/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/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/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt index d6faaee51087..b6f8326394fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt @@ -7,6 +7,8 @@ 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.panelstate.PanelExpansionChangeEvent import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager @@ -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/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/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/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/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/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt new file mode 100644 index 000000000000..d84010dc7771 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt @@ -0,0 +1,171 @@ +/* + * 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 android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.View +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +class StatusBarBoundsProviderTest : SysuiTestCase() { + + companion object { + private val START_SIDE_BOUNDS = Rect(50, 100, 150, 200) + private val END_SIDE_BOUNDS = Rect(250, 300, 350, 400) + } + + @Mock private lateinit var boundsChangeListener: BoundsChangeListener + + private lateinit var boundsProvider: StatusBarBoundsProvider + + private lateinit var startSideContent: View + private lateinit var endSideContent: View + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + startSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(START_SIDE_BOUNDS) } + endSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(END_SIDE_BOUNDS) } + + boundsProvider = + StatusBarBoundsProvider(setOf(boundsChangeListener), startSideContent, endSideContent) + } + + @Test + fun visibleStartSideBounds_returnsBoundsFromStartSideContentView() { + assertThat(boundsProvider.visibleStartSideBounds).isEqualTo(START_SIDE_BOUNDS) + } + + @Test + fun visibleEndSideBounds_returnsBoundsFromEndSideContentView() { + assertThat(boundsProvider.visibleEndSideBounds).isEqualTo(END_SIDE_BOUNDS) + } + + @Test + fun startBoundsChange_afterStart_notifiesListener() { + boundsProvider.start() + val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 } + + startSideContent.setBoundsOnScreen(newBounds) + + verify(boundsChangeListener).onStatusBarBoundsChanged() + } + + @Test + fun startBoundsChange_beforeStart_doesNotNotifyListener() { + val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 } + + startSideContent.setBoundsOnScreen(newBounds) + + verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + } + + @Test + fun startBoundsChange_afterStop_doesNotNotifyListener() { + boundsProvider.start() + boundsProvider.stop() + val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 } + + startSideContent.setBoundsOnScreen(newBounds) + + verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + } + + @Test + fun startLayoutChange_afterStart_boundsOnScreenSame_doesNotNotifyListener() { + boundsProvider.start() + val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 } + + startSideContent.layout(newBounds) + + verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + } + + @Test + fun endBoundsChange_afterStart_notifiesListener() { + boundsProvider.start() + val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 } + + endSideContent.setBoundsOnScreen(newBounds) + + verify(boundsChangeListener).onStatusBarBoundsChanged() + } + + @Test + fun endBoundsChange_beforeStart_doesNotNotifyListener() { + val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 } + + endSideContent.setBoundsOnScreen(newBounds) + + verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + } + + @Test + fun endBoundsChange_afterStop_doesNotNotifyListener() { + boundsProvider.start() + boundsProvider.stop() + val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 } + + endSideContent.setBoundsOnScreen(newBounds) + + verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + } + + @Test + fun endLayoutChange_afterStart_boundsOnScreenSame_doesNotNotifyListener() { + boundsProvider.start() + val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 } + + endSideContent.layout(newBounds) + + verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + } +} + +private fun View.setBoundsOnScreen(bounds: Rect) { + doAnswer { invocation -> + val boundsOutput = invocation.arguments[0] as Rect + boundsOutput.set(bounds) + return@doAnswer Unit + } + .`when`(this) + .getBoundsOnScreen(any()) + layout(bounds.left, bounds.top, bounds.right, bounds.bottom) +} + +private fun View.layout(rect: Rect) { + layout(rect.left, rect.top, rect.right, rect.bottom) +} 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/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 3f14494322e3..56804d58d241 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 @@ -41,6 +41,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; +import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogcatEchoTracker; @@ -107,6 +108,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private NotificationPanelViewController mNotificationPanelViewController; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; + @Mock + private DumpManager mDumpManager; public CollapsedStatusBarFragmentTest() { super(CollapsedStatusBarFragment.class); @@ -124,7 +127,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 +137,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 +237,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 +249,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 +260,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 +276,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()); } @@ -386,7 +389,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { ), mOperatorNameViewControllerFactory, mSecureSettings, - mExecutor); + mExecutor, + mDumpManager); } private void setUpDaggerComponent() { @@ -419,7 +423,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/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 2b37b9e5366e..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,9 +35,6 @@ 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; @@ -71,31 +66,25 @@ public class WMShellTest extends SysuiTestCase { @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, mShellInterface, 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, mScreenLifecycle, mSysUiState, + Optional.of(mSplitScreen), Optional.of(mOneHanded), + Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController, + mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor); } @@ -107,27 +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 initCompatUI_registersCallbacks() { - mWMShell.initCompatUi(mCompatUI); - - verify(mKeyguardStateController).addCallback(any(KeyguardStateController.Callback.class)); - } } diff --git a/services/contentcapture/Android.bp b/services/contentcapture/Android.bp index 434f239dbddc..5392c2cde3b8 100644 --- a/services/contentcapture/Android.bp +++ b/services/contentcapture/Android.bp @@ -17,6 +17,9 @@ filegroup { java_library_static { name: "services.contentcapture", defaults: ["platform_service_defaults"], - srcs: [":services.contentcapture-sources"], + srcs: [ + ":services.contentcapture-sources", + "java/**/*.logtags", + ], libs: ["services.core"], } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 41a759254909..0428b2322f27 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -60,6 +60,7 @@ import android.service.contentcapture.SnapshotData; import android.service.voice.VoiceInteractionManagerInternal; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -88,6 +89,11 @@ final class ContentCapturePerUserService private static final String TAG = ContentCapturePerUserService.class.getSimpleName(); + private static final int EVENT_LOG_CONNECT_STATE_DIED = 0; + static final int EVENT_LOG_CONNECT_STATE_CONNECTED = 1; + static final int EVENT_LOG_CONNECT_STATE_DISCONNECTED = 2; + + @GuardedBy("mLock") private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>(); @@ -190,9 +196,13 @@ final class ContentCapturePerUserService Slog.w(TAG, "remote service died: " + service); synchronized (mLock) { mZombie = true; + ComponentName serviceComponent = getServiceComponentName(); writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED, - getServiceComponentName()); + serviceComponent); + EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId, + serviceComponent != null ? serviceComponent.flattenToShortString() : "", + EVENT_LOG_CONNECT_STATE_DIED); } } @@ -614,11 +624,16 @@ final class ContentCapturePerUserService ? "null_activities" : activities.size() + " activities") + ")" + " for user " + mUserId); } + int packageCount = packages != null ? packages.size() : 0; + int activityCount = activities != null ? activities.size() : 0; ArraySet<String> oldList = mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId); + EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, oldList.size()); mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); + EventLog.writeEvent(EventLogTags.CC_SET_ALLOWLIST, mUserId, + packageCount, activityCount); writeSetWhitelistEvent(getServiceComponentName(), packages, activities); updateContentCaptureOptions(oldList); @@ -699,12 +714,14 @@ final class ContentCapturePerUserService private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) { ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions .getWhitelistedPackages(mUserId); + EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, adding.size()); if (oldList != null && adding != null) { adding.removeAll(oldList); } int N = adding != null ? adding.size() : 0; + EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, N); for (int i = 0; i < N; i++) { String packageName = adding.valueAt(i); ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions diff --git a/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags new file mode 100644 index 000000000000..6722b9ed3c5f --- /dev/null +++ b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags @@ -0,0 +1,8 @@ +# See system/logging/logcat/event.logtags for a description of the format of this file. + +option java_package com.android.server.contentcapture + +53200 cc_connect_state_changed (User|1|5),(component|3),(type|1) +53201 cc_set_allowlist (User|1|5),(package_count|1),(activity_count|1) +53202 cc_current_allowlist (User|1|5),(count|1) +53203 cc_update_options (User|1|5),(count|1) diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index 1efe55aa0767..e22a9d07b4ae 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -31,6 +31,7 @@ import android.service.contentcapture.IContentCaptureService; import android.service.contentcapture.IContentCaptureServiceCallback; import android.service.contentcapture.IDataShareCallback; import android.service.contentcapture.SnapshotData; +import android.util.EventLog; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.DataRemovalRequest; @@ -47,6 +48,7 @@ final class RemoteContentCaptureService private final IBinder mServerCallback; private final int mIdleUnbindTimeoutMs; private final ContentCapturePerUserService mPerUserService; + private final int mUserId; RemoteContentCaptureService(Context context, String serviceInterface, ComponentName serviceComponentName, IContentCaptureServiceCallback callback, int userId, @@ -61,6 +63,7 @@ final class RemoteContentCaptureService mPerUserService = perUserService; mServerCallback = callback.asBinder(); mIdleUnbindTimeoutMs = idleUnbindTimeoutMs; + mUserId = userId; // Bind right away, which will trigger a onConnected() on service's ensureBoundLocked(); @@ -88,6 +91,9 @@ final class RemoteContentCaptureService writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED, mComponentName); + EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId, + mComponentName != null ? mComponentName.flattenToShortString() : "", + ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_CONNECTED); } finally { // Update the system-service state, in case the service reconnected after // dying @@ -98,6 +104,9 @@ final class RemoteContentCaptureService writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED, mComponentName); + EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId, + mComponentName != null ? mComponentName.flattenToShortString() : "", + ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_DISCONNECTED); } } catch (Exception e) { Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e); 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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 0040ea9215b3..3a869f859e52 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -9181,11 +9181,8 @@ public class AudioService extends IAudioService.Stub Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT); if (settings == null) { Log.e(TAG, "error reading spatial audio device settings"); - } else { - Log.v(TAG, "restoring spatial audio device settings: " + settings); - mSpatializerHelper.setSADeviceSettings(settings); } - mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect); + mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings); mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect); } @@ -10373,6 +10370,11 @@ public class AudioService extends IAudioService.Stub @Override public void setAccessibilityServiceUids(IntArray uids) { + // TODO(b/233287010): Fix voice interaction and a11y concurrency in audio policy service + if (isPlatformAutomotive()) { + return; + } + synchronized (mAccessibilityServiceUidsLock) { if (uids.size() == 0) { mAccessibilityServiceUids = null; diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index dd44af1b68ee..cd5960ffbf32 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -175,7 +175,7 @@ public class SpatializerHelper { mASA = asa; } - synchronized void init(boolean effectExpected) { + synchronized void init(boolean effectExpected, @Nullable String settings) { loglogi("init effectExpected=" + effectExpected); if (!effectExpected) { loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected"); @@ -278,6 +278,13 @@ public class SpatializerHelper { mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i)); } } + + // When initialized from AudioService, the settings string will be non-null. + // Saved settings need to be applied after spatialization support is initialized above. + if (settings != null) { + setSADeviceSettings(settings); + } + // for both transaural / binaural, we are not forcing enablement as the init() method // could have been called another time after boot in case of audioserver restart addCompatibleAudioDevice( @@ -316,7 +323,7 @@ public class SpatializerHelper { mState = STATE_UNINITIALIZED; mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; - init(true); + init(true, null /* settings */); setSpatializerEnabledInt(featureEnabled); } @@ -731,7 +738,7 @@ public class SpatializerHelper { return; } if (mState == STATE_UNINITIALIZED) { - init(true); + init(true, null /* settings */); } setSpatializerEnabledInt(true); } else { 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/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 2322280d8a9b..b5aa7b14792b 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -287,7 +287,8 @@ public class DisplayDeviceConfig { private BrightnessThrottlingData mBrightnessThrottlingData; private BrightnessThrottlingData mOriginalBrightnessThrottlingData; - private DisplayDeviceConfig(Context context) { + @VisibleForTesting + DisplayDeviceConfig(Context context) { mContext = context; } @@ -691,7 +692,8 @@ public class DisplayDeviceConfig { return config; } - private boolean initFromFile(File configFile) { + @VisibleForTesting + boolean initFromFile(File configFile) { if (!configFile.exists()) { // Display configuration files aren't required to exist. return false; 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 9486a45e989e..e589080786b5 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2459,11 +2459,11 @@ public class NotificationManagerService extends SystemService { SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> { try { if (DBG) { - Slog.d(TAG, "Reposting " + r.getKey()); + Slog.d(TAG, "Reposting " + r.getKey() + " " + muteOnReturn); } enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(), r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(), - r.getSbn().getId(), r.getSbn().getNotification(), userId, true); + r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn); } catch (Exception e) { Slog.e(TAG, "Cannot un-snooze notification", e); } diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 0dc188b75d5e..46f0dbcdfe10 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -427,8 +427,7 @@ final class ScanPackageUtils { pkgSetting.setLastModifiedTime(scanFileTime); // TODO(b/135203078): Remove, move to constructor pkgSetting.setPkg(parsedPackage) - .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting)) - .setPrivateFlags( + .setPkgFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting), PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting)); if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) { pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode()); diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index b952f80850bb..61a251e19db7 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -146,6 +146,17 @@ public abstract class SettingBase implements Watchable, Snappable { return this; } + /** + * Unconditionally set both mPkgFlags and mPkgPrivateFlags. + * Should not be used outside pkgSetting initialization or update. + */ + SettingBase setPkgFlags(int flags, int privateFlags) { + this.mPkgFlags = flags; + this.mPkgPrivateFlags = privateFlags; + onChanged(); + return this; + } + public int getFlags() { return mPkgFlags; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 6400502f1a89..7437b145189f 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -987,8 +987,7 @@ public final class Settings implements Watchable, Snappable { // Update new package state. .setLastModifiedTime(codePath.lastModified()) .setDomainSetId(domainSetId); - pkgSetting.setFlags(pkgFlags) - .setPrivateFlags(pkgPrivateFlags); + pkgSetting.setPkgFlags(pkgFlags, pkgPrivateFlags); } else { pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi, @@ -1175,15 +1174,15 @@ public final class Settings implements Watchable, Snappable { .setUsesStaticLibrariesVersions(null); } - // These two flags are preserved from the existing PackageSetting. Copied from prior code, - // unclear if this is actually necessary. - boolean wasExternalStorage = (pkgSetting.getFlags() - & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; - if (wasExternalStorage) { - pkgFlags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; - } else { - pkgFlags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE; - } + // If what we are scanning is a system (and possibly privileged) package, + // then make it so, regardless of whether it was previously installed only + // in the data partition. Reset first. + int newPkgFlags = pkgSetting.getFlags(); + newPkgFlags &= ~ApplicationInfo.FLAG_SYSTEM; + newPkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM; + // Only set pkgFlags. + pkgSetting.setPkgFlags(newPkgFlags, pkgSetting.getPrivateFlags()); + boolean wasRequiredForSystemUser = (pkgSetting.getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0; if (wasRequiredForSystemUser) { @@ -1191,9 +1190,7 @@ public final class Settings implements Watchable, Snappable { } else { pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER; } - - pkgSetting.setFlags(pkgFlags) - .setPrivateFlags(pkgPrivateFlags); + pkgSetting.setPrivateFlags(pkgPrivateFlags); } /** 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3676ffb6336c..0a58044d66c6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -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 diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index fe66f7abe90f..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; @@ -6520,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/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/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/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 08bf7bca93c4..53f1fe6abec5 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -352,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/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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d31dfeed389d..9d6e250e8a47 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8262,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/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/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java new file mode 100644 index 000000000000..793930395daa --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -0,0 +1,182 @@ +/* + * 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.display; + + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public final class DisplayDeviceConfigTest { + private DisplayDeviceConfig mDisplayDeviceConfig; + @Mock + private Context mContext; + + @Mock + private Resources mResources; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + when(mContext.getResources()).thenReturn(mResources); + mockDeviceConfigs(); + try { + Path tempFile = Files.createTempFile("display_config", ".tmp"); + Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8)); + mDisplayDeviceConfig = new DisplayDeviceConfig(mContext); + mDisplayDeviceConfig.initFromFile(tempFile.toFile()); + } catch (IOException e) { + throw new IOException("Failed to setup the display device config.", e); + } + } + + @Test + public void testConfigValues() { + assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000); + assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50); + assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000); + assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000); + assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f); + assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f); + assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f); + assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f); + assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f); + assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f); + assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f); + assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f}, + 0.0f); + assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.0f); + assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f}, + 0.0f); + assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f); + assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f); + + // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping, + // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. + // Also add test for the case where optional display configs are null + } + + private String getContent() { + return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<displayConfiguration>\n" + + "<screenBrightnessMap>\n" + + "<point>\n" + + "<value>0.0</value>\n" + + "<nits>2.0</nits>\n" + + "</point>\n" + + "<point>\n" + + "<value>0.62</value>\n" + + "<nits>500.0</nits>\n" + + "</point>\n" + + "<point>\n" + + "<value>1.0</value>\n" + + "<nits>800.0</nits>\n" + + "</point>\n" + + "</screenBrightnessMap>\n" + + "<highBrightnessMode enabled=\"true\">\n" + + "<transitionPoint>0.62</transitionPoint>\n" + + "<minimumLux>10000</minimumLux>\n" + + "<timing>\n" + + "<!-- allow for 5 minutes out of every 30 minutes -->\n" + + "<timeWindowSecs>1800</timeWindowSecs>\n" + + "<timeMaxSecs>300</timeMaxSecs>\n" + + "<timeMinSecs>60</timeMinSecs>\n" + + "</timing>\n" + + "<refreshRate>\n" + + "<minimum>120</minimum>\n" + + "<maximum>120</maximum>\n" + + "</refreshRate>\n" + + "<thermalStatusLimit>light</thermalStatusLimit>\n" + + "<allowInLowPowerMode>false</allowInLowPowerMode>\n" + + "</highBrightnessMode>\n" + + "<ambientBrightnessChangeThresholds>\n" + + "<brighteningThresholds>\n" + + "<minimum>10</minimum>\n" + + "</brighteningThresholds>\n" + + "<darkeningThresholds>\n" + + "<minimum>2</minimum>\n" + + "</darkeningThresholds>\n" + + "</ambientBrightnessChangeThresholds>\n" + + "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> " + + "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease> " + + "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>" + + "<screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>" + + "<screenBrightnessRampIncreaseMaxMillis>" + + "2000" + + "</screenBrightnessRampIncreaseMaxMillis>" + + "<screenBrightnessRampDecreaseMaxMillis>" + + "3000" + + "</screenBrightnessRampDecreaseMaxMillis>" + + "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n" + + "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n" + + "<displayBrightnessChangeThresholds>" + + "<brighteningThresholds>" + + "<minimum>" + + "0.001" + + "</minimum>" + + "</brighteningThresholds>" + + "<darkeningThresholds>" + + "<minimum>" + + "0.002" + + "</minimum>" + + "</darkeningThresholds>" + + "</displayBrightnessChangeThresholds>" + + "<screenBrightnessRampIncreaseMaxMillis>" + + "2000" + + "</screenBrightnessRampIncreaseMaxMillis>\n" + + "<thermalThrottling>\n" + + "<brightnessThrottlingMap>\n" + + "<brightnessThrottlingPoint>\n" + + "<thermalStatus>emergency</thermalStatus>\n" + + "<!-- Throttling to 250 nits: (250-2.0)/(500-2.0)*(0.62-0.0)+0" + + ".0 = 0.30875502 -->\n" + + "<brightness>0.30875502</brightness>\n" + + "</brightnessThrottlingPoint>\n" + + "</brightnessThrottlingMap>\n" + + "</thermalThrottling>\n" + + "</displayConfiguration>\n"; + } + + private void mockDeviceConfigs() { + when(mResources.getFloat(com.android.internal.R.dimen + .config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f); + when(mResources.getFloat(com.android.internal.R.dimen + .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f); + } +} 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/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/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 8a539cd3ddff..0cbf1b2c7cc8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -811,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/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>(); } /** |