diff options
447 files changed, 9445 insertions, 2841 deletions
diff --git a/apct-tests/perftests/autofill/AndroidManifest.xml b/apct-tests/perftests/autofill/AndroidManifest.xml index 57595a213d20..de2a3f2c4bfc 100644 --- a/apct-tests/perftests/autofill/AndroidManifest.xml +++ b/apct-tests/perftests/autofill/AndroidManifest.xml @@ -16,6 +16,16 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.perftests.autofill"> + <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <uses-permission android:name="android.permission.DEVICE_POWER" /> + <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + <uses-permission android:name="android.permission.REAL_GET_TASKS" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <application> <uses-library android:name="android.test.runner" /> <activity android:name="android.perftests.utils.PerfTestActivity" diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index bd9b6e952118..ddde27220b96 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -368,7 +368,8 @@ public class TaskInfo { && Objects.equals(taskDescription, that.taskDescription) && isFocused == that.isFocused && isVisible == that.isVisible - && isSleeping == that.isSleeping; + && isSleeping == that.isSleeping + && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId); } /** diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index b570ae60ee91..11c01e61911c 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -586,12 +586,12 @@ public class WallpaperManager { Rect dimensions = null; synchronized (this) { + ParcelFileDescriptor pfd = null; try { Bundle params = new Bundle(); + pfd = mService.getWallpaperWithFeature(context.getOpPackageName(), + context.getAttributionTag(), this, FLAG_SYSTEM, params, userId); // Let's peek user wallpaper first. - ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( - context.getOpPackageName(), context.getAttributionTag(), this, - FLAG_SYSTEM, params, userId); if (pfd != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; @@ -600,6 +600,13 @@ public class WallpaperManager { } } catch (RemoteException ex) { Log.w(TAG, "peek wallpaper dimensions failed", ex); + } finally { + if (pfd != null) { + try { + pfd.close(); + } catch (IOException ignored) { + } + } } } // If user wallpaper is unavailable, may be the default one instead. diff --git a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl index a630873c7f67..e1c13f7fc9e1 100644 --- a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl +++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl @@ -29,4 +29,6 @@ oneway interface ICompanionDeviceDiscoveryService { in String callingPackage, in IFindDeviceCallback findCallback, in AndroidFuture<Association> serviceCallback); + + void onAssociationCreated(); } diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java index 065ae64a92ad..603b06ddabaa 100644 --- a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java +++ b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java @@ -38,16 +38,13 @@ public interface BiometricOverlayConstants { int REASON_AUTH_KEYGUARD = 4; /** Non-specific usage (from FingerprintManager). */ int REASON_AUTH_OTHER = 5; - /** Usage from Settings. */ - int REASON_AUTH_SETTINGS = 6; @IntDef({REASON_UNKNOWN, REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING, REASON_AUTH_BP, REASON_AUTH_KEYGUARD, - REASON_AUTH_OTHER, - REASON_AUTH_SETTINGS}) + REASON_AUTH_OTHER}) @Retention(RetentionPolicy.SOURCE) @interface ShowReason {} } diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 06861046f2c7..02b2c5d5db84 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -124,7 +124,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public void setControl(@Nullable InsetsSourceControl control, int[] showTypes, int[] hideTypes) { super.setControl(control, showTypes, hideTypes); - if (control == null && !mIsRequestedVisibleAwaitingControl) { + if (control == null && !isRequestedVisibleAwaitingControl()) { hide(); removeSurface(); } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 7d8d653f5ab3..805727c871b2 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -284,8 +284,8 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro mShownOnFinish, mCurrentAlpha, mCurrentInsets)); mController.notifyFinished(this, mShownOnFinish); releaseLeashes(); + if (DEBUG) Log.d(TAG, "Animation finished abruptly."); } - if (DEBUG) Log.d(TAG, "Animation finished abruptly."); return mFinished; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4c0c82fe5c2e..65d0e497a5b1 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -879,6 +879,13 @@ public final class ViewRootImpl implements ViewParent, } } + /** Remove a static config callback. */ + public static void removeConfigCallback(ConfigChangedCallback callback) { + synchronized (sConfigCallbacks) { + sConfigCallbacks.remove(callback); + } + } + /** Add activity config callback to be notified about override config changes. */ public void setActivityConfigCallback(ActivityConfigCallback callback) { mActivityConfigCallback = callback; @@ -4164,11 +4171,6 @@ public final class ViewRootImpl implements ViewParent, } }); }); - } else if (reportNextDraw) { - // If we need to report next draw, wait for adapter to flush its shadow queue - // by processing previously queued buffers so that we can submit the - // transaction a timely manner. - mBlastBufferQueue.flushShadowQueue(); } }; registerRtFrameCallback(frameDrawingCallback); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 2357d13c8d41..57b7d618917d 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -5746,11 +5746,9 @@ public class RemoteViews implements Parcelable, Filter { // persisted across change, and has the RemoteViews re-applied in a different situation // (orientation or size), we throw an exception, since the layouts may be completely // unrelated. - if (hasMultipleLayouts()) { - if (!rvToApply.canRecycleView(v)) { - throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + - " that does not share the same root layout id."); - } + if (!rvToApply.canRecycleView(v)) { + throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + + " that does not share the same root layout id."); } rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); @@ -5794,11 +5792,9 @@ public class RemoteViews implements Parcelable, Filter { // In the case that a view has this RemoteViews applied in one orientation, is persisted // across orientation change, and has the RemoteViews re-applied in the new orientation, // we throw an exception, since the layouts may be completely unrelated. - if (hasMultipleLayouts()) { - if (!rvToApply.canRecycleView(v)) { - throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + - " that does not share the same root layout id."); - } + if (!rvToApply.canRecycleView(v)) { + throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + + " that does not share the same root layout id."); } return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), diff --git a/core/java/android/window/RemoteTransition.java b/core/java/android/window/RemoteTransition.java index b243b656b8cd..4bd15f27a91a 100644 --- a/core/java/android/window/RemoteTransition.java +++ b/core/java/android/window/RemoteTransition.java @@ -18,6 +18,7 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.IApplicationThread; import android.os.IBinder; import android.os.Parcelable; @@ -34,6 +35,14 @@ public class RemoteTransition implements Parcelable { /** The actual remote-transition interface used to run the transition animation. */ private @NonNull IRemoteTransition mRemoteTransition; + /** The application thread that will be running the remote transition. */ + private @Nullable IApplicationThread mAppThread; + + /** Constructs with no app thread (animation runs in shell). */ + public RemoteTransition(@NonNull IRemoteTransition remoteTransition) { + this(remoteTransition, null /* appThread */); + } + /** Get the IBinder associated with the underlying IRemoteTransition. */ public @Nullable IBinder asBinder() { return mRemoteTransition.asBinder(); @@ -59,13 +68,17 @@ public class RemoteTransition implements Parcelable { * * @param remoteTransition * The actual remote-transition interface used to run the transition animation. + * @param appThread + * The application thread that will be running the remote transition. */ @DataClass.Generated.Member public RemoteTransition( - @NonNull IRemoteTransition remoteTransition) { + @NonNull IRemoteTransition remoteTransition, + @Nullable IApplicationThread appThread) { this.mRemoteTransition = remoteTransition; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mRemoteTransition); + this.mAppThread = appThread; // onConstructed(); // You can define this method to get a callback } @@ -79,6 +92,14 @@ public class RemoteTransition implements Parcelable { } /** + * The application thread that will be running the remote transition. + */ + @DataClass.Generated.Member + public @Nullable IApplicationThread getAppThread() { + return mAppThread; + } + + /** * The actual remote-transition interface used to run the transition animation. */ @DataClass.Generated.Member @@ -89,6 +110,15 @@ public class RemoteTransition implements Parcelable { return this; } + /** + * The application thread that will be running the remote transition. + */ + @DataClass.Generated.Member + public @NonNull RemoteTransition setAppThread(@NonNull IApplicationThread value) { + mAppThread = value; + return this; + } + @Override @DataClass.Generated.Member public String toString() { @@ -96,7 +126,8 @@ public class RemoteTransition implements Parcelable { // String fieldNameToString() { ... } return "RemoteTransition { " + - "remoteTransition = " + mRemoteTransition + + "remoteTransition = " + mRemoteTransition + ", " + + "appThread = " + mAppThread + " }"; } @@ -106,7 +137,11 @@ public class RemoteTransition implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } + byte flg = 0; + if (mAppThread != null) flg |= 0x2; + dest.writeByte(flg); dest.writeStrongInterface(mRemoteTransition); + if (mAppThread != null) dest.writeStrongInterface(mAppThread); } @Override @@ -120,11 +155,14 @@ public class RemoteTransition implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } + byte flg = in.readByte(); IRemoteTransition remoteTransition = IRemoteTransition.Stub.asInterface(in.readStrongBinder()); + IApplicationThread appThread = (flg & 0x2) == 0 ? null : IApplicationThread.Stub.asInterface(in.readStrongBinder()); this.mRemoteTransition = remoteTransition; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mRemoteTransition); + this.mAppThread = appThread; // onConstructed(); // You can define this method to get a callback } @@ -144,10 +182,10 @@ public class RemoteTransition implements Parcelable { }; @DataClass.Generated( - time = 1630613039043L, + time = 1630690027011L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/RemoteTransition.java", - inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") + inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index f748d4bc121d..f04155d112d4 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -20,6 +20,10 @@ import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACK import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_AVD; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; @@ -54,6 +58,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import com.android.internal.R; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.DecorView; import com.android.internal.util.ContrastColorUtil; @@ -487,6 +492,23 @@ public final class SplashScreenView extends FrameLayout { } IconAnimateListener aniDrawable = (IconAnimateListener) iconDrawable; aniDrawable.prepareAnimate(duration, this::animationStartCallback); + aniDrawable.setAnimationJankMonitoring(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + InteractionJankMonitor.getInstance().cancel(CUJ_SPLASHSCREEN_AVD); + } + + @Override + public void onAnimationEnd(Animator animation) { + InteractionJankMonitor.getInstance().end(CUJ_SPLASHSCREEN_AVD); + } + + @Override + public void onAnimationStart(Animator animation) { + InteractionJankMonitor.getInstance().begin( + SplashScreenView.this, CUJ_SPLASHSCREEN_AVD); + } + }); } private void animationStartCallback() { @@ -669,6 +691,12 @@ public final class SplashScreenView extends FrameLayout { * Stop animation. */ void stopAnimation(); + + /** + * Provides a chance to start interaction jank monitoring in avd animation. + * @param listener a listener to start jank monitoring + */ + default void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {} } /** diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index c863292d4ea0..d14054d4f9f7 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -99,6 +99,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback; private final Handler mHandler; private final ChoreographerWrapper mChoreographer; + private final Object mLock = InteractionJankMonitor.getInstance().getLock(); @VisibleForTesting public final boolean mSurfaceOnly; @@ -181,7 +182,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() { @Override public void surfaceCreated(SurfaceControl.Transaction t) { - synchronized (FrameTracker.this) { + synchronized (mLock) { if (mSurfaceControl == null) { mSurfaceControl = mViewRoot.getSurfaceControl(); if (mBeginVsyncId != INVALID_ID) { @@ -203,12 +204,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener // Wait a while to give the system a chance for the remaining // frames to arrive, then force finish the session. mHandler.postDelayed(() -> { - synchronized (FrameTracker.this) { + synchronized (mLock) { if (DEBUG) { Log.d(TAG, "surfaceDestroyed: " + mSession.getName() + ", finalized=" + mMetricsFinalized + ", info=" + mJankInfos.size() - + ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId); + + ", vsync=" + mBeginVsyncId); } if (!mMetricsFinalized) { end(REASON_END_SURFACE_DESTROYED); @@ -227,20 +228,20 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener /** * Begin a trace session of the CUJ. */ - public synchronized void begin() { - mBeginVsyncId = mChoreographer.getVsyncId() + 1; - if (DEBUG) { - Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId); - } - if (mSurfaceControl != null) { - postTraceStartMarker(); - mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); - } - if (!mSurfaceOnly) { - mRendererWrapper.addObserver(mObserver); - } - if (mListener != null) { - mListener.onCujEvents(mSession, ACTION_SESSION_BEGIN); + public void begin() { + synchronized (mLock) { + mBeginVsyncId = mChoreographer.getVsyncId() + 1; + if (DEBUG) { + Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId); + } + if (mSurfaceControl != null) { + postTraceStartMarker(); + mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); + } + if (!mSurfaceOnly) { + mRendererWrapper.addObserver(mObserver); + } + notifyCujEvent(ACTION_SESSION_BEGIN); } } @@ -250,7 +251,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener @VisibleForTesting public void postTraceStartMarker() { mChoreographer.mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, () -> { - synchronized (FrameTracker.this) { + synchronized (mLock) { if (mCancelled || mEndVsyncId != INVALID_ID) { return; } @@ -263,88 +264,98 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener /** * End the trace session of the CUJ. */ - public synchronized void end(@Reasons int reason) { - if (mEndVsyncId != INVALID_ID) return; - mEndVsyncId = mChoreographer.getVsyncId(); - - // Cancel the session if: - // 1. The session begins and ends at the same vsync id. - // 2. The session never begun. - if (mBeginVsyncId == INVALID_ID) { - cancel(REASON_CANCEL_NOT_BEGUN); - } else if (mEndVsyncId <= mBeginVsyncId) { - cancel(REASON_CANCEL_SAME_VSYNC); - } else { - if (DEBUG) { - Log.d(TAG, "end: " + mSession.getName() - + ", end=" + mEndVsyncId + ", reason=" + reason); - } - Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); - mSession.setReason(reason); - if (mListener != null) { - mListener.onCujEvents(mSession, ACTION_SESSION_END); + public boolean end(@Reasons int reason) { + synchronized (mLock) { + if (mCancelled || mEndVsyncId != INVALID_ID) return false; + mEndVsyncId = mChoreographer.getVsyncId(); + // Cancel the session if: + // 1. The session begins and ends at the same vsync id. + // 2. The session never begun. + if (mBeginVsyncId == INVALID_ID) { + return cancel(REASON_CANCEL_NOT_BEGUN); + } else if (mEndVsyncId <= mBeginVsyncId) { + return cancel(REASON_CANCEL_SAME_VSYNC); + } else { + if (DEBUG) { + Log.d(TAG, "end: " + mSession.getName() + + ", end=" + mEndVsyncId + ", reason=" + reason); + } + Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); + mSession.setReason(reason); + + // We don't remove observer here, + // will remove it when all the frame metrics in this duration are called back. + // See onFrameMetricsAvailable for the logic of removing the observer. + // Waiting at most 10 seconds for all callbacks to finish. + mWaitForFinishTimedOut = () -> { + Log.e(TAG, "force finish cuj because of time out:" + mSession.getName()); + finish(mJankInfos.size() - 1); + }; + mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10)); + notifyCujEvent(ACTION_SESSION_END); + return true; } - - // We don't remove observer here, - // will remove it when all the frame metrics in this duration are called back. - // See onFrameMetricsAvailable for the logic of removing the observer. - // Waiting at most 10 seconds for all callbacks to finish. - mWaitForFinishTimedOut = () -> { - Log.e(TAG, "force finish cuj because of time out:" + mSession.getName()); - finish(mJankInfos.size() - 1); - }; - mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10)); } } /** * Cancel the trace session of the CUJ. */ - public synchronized void cancel(@Reasons int reason) { - mCancelled = true; + public boolean cancel(@Reasons int reason) { + synchronized (mLock) { + final boolean cancelFromEnd = + reason == REASON_CANCEL_NOT_BEGUN || reason == REASON_CANCEL_SAME_VSYNC; + if (mCancelled || (mEndVsyncId != INVALID_ID && !cancelFromEnd)) return false; + mCancelled = true; + // We don't need to end the trace section if it never begun. + if (mTracingStarted) { + Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); + } - // We don't need to end the trace section if it never begun. - if (mTracingStarted) { - Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); - } + // Always remove the observers in cancel call to avoid leakage. + removeObservers(); - // Always remove the observers in cancel call to avoid leakage. - removeObservers(); + if (DEBUG) { + Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId + + ", end=" + mEndVsyncId + ", reason=" + reason); + } - if (DEBUG) { - Log.d(TAG, "cancel: " + mSession.getName() - + ", begin=" + mBeginVsyncId + ", end=" + mEndVsyncId + ", reason=" + reason); + mSession.setReason(reason); + // Notify the listener the session has been cancelled. + // We don't notify the listeners if the session never begun. + notifyCujEvent(ACTION_SESSION_CANCEL); + return true; } + } - mSession.setReason(reason); - // Notify the listener the session has been cancelled. - // We don't notify the listeners if the session never begun. - if (mListener != null) { - mListener.onCujEvents(mSession, ACTION_SESSION_CANCEL); - } + private void notifyCujEvent(String action) { + if (mListener == null) return; + mListener.onCujEvents(mSession, action); } @Override - public synchronized void onJankDataAvailable(SurfaceControl.JankData[] jankData) { - if (mCancelled) { - return; - } - - for (SurfaceControl.JankData jankStat : jankData) { - if (!isInRange(jankStat.frameVsyncId)) { - continue; + public void onJankDataAvailable(SurfaceControl.JankData[] jankData) { + synchronized (mLock) { + if (mCancelled) { + return; } - JankInfo info = findJankInfo(jankStat.frameVsyncId); - if (info != null) { - info.surfaceControlCallbackFired = true; - info.jankType = jankStat.jankType; - } else { - mJankInfos.put((int) jankStat.frameVsyncId, - JankInfo.createFromSurfaceControlCallback( - jankStat.frameVsyncId, jankStat.jankType)); + + for (SurfaceControl.JankData jankStat : jankData) { + if (!isInRange(jankStat.frameVsyncId)) { + continue; + } + JankInfo info = findJankInfo(jankStat.frameVsyncId); + if (info != null) { + info.surfaceControlCallbackFired = true; + info.jankType = jankStat.jankType; + } else { + mJankInfos.put((int) jankStat.frameVsyncId, + JankInfo.createFromSurfaceControlCallback( + jankStat.frameVsyncId, jankStat.jankType)); + } } + processJankInfos(); } - processJankInfos(); } private @Nullable JankInfo findJankInfo(long frameVsyncId) { @@ -359,31 +370,34 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } @Override - public synchronized void onFrameMetricsAvailable(int dropCountSinceLastInvocation) { - if (mCancelled) { - return; - } + public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) { + synchronized (mLock) { + if (mCancelled) { + return; + } - // Since this callback might come a little bit late after the end() call. - // We should keep tracking the begin / end timestamp. - // Then compare with vsync timestamp to check if the frame is in the duration of the CUJ. - long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION); - boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1; - long frameVsyncId = mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID]; + // Since this callback might come a little bit late after the end() call. + // We should keep tracking the begin / end timestamp that we can compare with + // vsync timestamp to check if the frame is in the duration of the CUJ. + long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION); + boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1; + long frameVsyncId = + mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID]; - if (!isInRange(frameVsyncId)) { - return; - } - JankInfo info = findJankInfo(frameVsyncId); - if (info != null) { - info.hwuiCallbackFired = true; - info.totalDurationNanos = totalDurationNanos; - info.isFirstFrame = isFirstFrame; - } else { - mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback( - frameVsyncId, totalDurationNanos, isFirstFrame)); + if (!isInRange(frameVsyncId)) { + return; + } + JankInfo info = findJankInfo(frameVsyncId); + if (info != null) { + info.hwuiCallbackFired = true; + info.totalDurationNanos = totalDurationNanos; + info.isFirstFrame = isFirstFrame; + } else { + mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback( + frameVsyncId, totalDurationNanos, isFirstFrame)); + } + processJankInfos(); } - processJankInfos(); } /** @@ -497,11 +511,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND)); // Trigger perfetto if necessary. - boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1 - && missedFramesCount >= mTraceThresholdMissedFrames; - boolean overFrameTimeThreshold = !mSurfaceOnly && mTraceThresholdFrameTimeMillis != -1 - && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND; - if (overMissedFramesThreshold || overFrameTimeThreshold) { + if (shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) { triggerPerfetto(); } if (mSession.logToStatsd()) { @@ -513,9 +523,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */ missedSfFramesCount, missedAppFramesCount); - if (mListener != null) { - mListener.onCujEvents(mSession, ACTION_METRICS_LOGGED); - } + notifyCujEvent(ACTION_METRICS_LOGGED); } if (DEBUG) { Log.i(TAG, "finish: CUJ=" + mSession.getName() @@ -528,6 +536,14 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } } + private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) { + boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1 + && missedFramesCount >= mTraceThresholdMissedFrames; + boolean overFrameTimeThreshold = !mSurfaceOnly && mTraceThresholdFrameTimeMillis != -1 + && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND; + return overMissedFramesThreshold || overFrameTimeThreshold; + } + /** * Remove all the registered listeners, observers and callbacks. */ diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index f8eb95cbd48c..0ba5a398bb67 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -58,6 +58,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; @@ -173,6 +175,8 @@ public class InteractionJankMonitor { public static final int CUJ_PIP_TRANSITION = 35; public static final int CUJ_WALLPAPER_TRANSITION = 36; public static final int CUJ_USER_SWITCH = 37; + public static final int CUJ_SPLASHSCREEN_AVD = 38; + public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39; private static final int NO_STATSD_LOGGING = -1; @@ -219,6 +223,8 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM, }; private static volatile InteractionJankMonitor sInstance; @@ -230,6 +236,7 @@ public class InteractionJankMonitor { private final SparseArray<FrameTracker> mRunningTrackers; private final SparseArray<Runnable> mTimeoutActions; private final HandlerThread mWorker; + private final Object mLock = new Object(); private boolean mEnabled = DEFAULT_ENABLED; private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; @@ -276,6 +283,8 @@ public class InteractionJankMonitor { CUJ_PIP_TRANSITION, CUJ_WALLPAPER_TRANSITION, CUJ_USER_SWITCH, + CUJ_SPLASHSCREEN_AVD, + CUJ_SPLASHSCREEN_EXIT_ANIM, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -325,6 +334,10 @@ public class InteractionJankMonitor { mPropertiesChangedListener); } + Object getLock() { + return mLock; + } + /** * Creates a {@link FrameTracker} instance. * @@ -344,7 +357,7 @@ public class InteractionJankMonitor { final ChoreographerWrapper choreographer = new ChoreographerWrapper(Choreographer.getInstance()); - synchronized (this) { + synchronized (mLock) { FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(config.getContext(), act, s); return new FrameTracker(session, mWorker.getThreadHandler(), @@ -372,11 +385,16 @@ public class InteractionJankMonitor { final boolean badEnd = action.equals(ACTION_SESSION_END) && session.getReason() != REASON_END_NORMAL; final boolean badCancel = action.equals(ACTION_SESSION_CANCEL) - && session.getReason() != REASON_CANCEL_NORMAL; + && !(session.getReason() == REASON_CANCEL_NORMAL + || session.getReason() == REASON_CANCEL_TIMEOUT); return badEnd || badCancel; } - private void notifyEvents(Context context, String action, Session session) { + /** + * Notifies who may interest in some CUJ events. + */ + @VisibleForTesting + public void notifyEvents(Context context, String action, Session session) { if (action.equals(ACTION_SESSION_CANCEL) && session.getReason() == REASON_CANCEL_NOT_BEGUN) { return; @@ -389,7 +407,7 @@ public class InteractionJankMonitor { } private void removeTimeout(@CujType int cujType) { - synchronized (this) { + synchronized (mLock) { Runnable timeout = mTimeoutActions.get(cujType); if (timeout != null) { mWorker.getThreadHandler().removeCallbacks(timeout); @@ -432,17 +450,9 @@ public class InteractionJankMonitor { } private boolean beginInternal(@NonNull Configuration conf) { - synchronized (this) { + synchronized (mLock) { int cujType = conf.mCujType; - boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; - if (!mEnabled || !shouldSample) { - if (DEBUG) { - Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType) - + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED - + ", sample=" + shouldSample + ", interval=" + mSamplingInterval); - } - return false; - } + if (!shouldMonitor(cujType)) return false; FrameTracker tracker = getTracker(cujType); // Skip subsequent calls if we already have an ongoing tracing. if (tracker != null) return false; @@ -460,6 +470,24 @@ public class InteractionJankMonitor { } /** + * Check if the monitoring is enabled and if it should be sampled. + */ + @SuppressWarnings("RandomModInteger") + @VisibleForTesting + public boolean shouldMonitor(@CujType int cujType) { + boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; + if (!mEnabled || !shouldSample) { + if (DEBUG) { + Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType) + + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED + + ", sample=" + shouldSample + ", interval=" + mSamplingInterval); + } + return false; + } + return true; + } + + /** * Schedules a timeout action. * @param cuj cuj type * @param timeout duration to timeout @@ -478,14 +506,16 @@ public class InteractionJankMonitor { * @return boolean true if the tracker is ended successfully, false otherwise. */ public boolean end(@CujType int cujType) { - synchronized (this) { + synchronized (mLock) { // remove the timeout action first. removeTimeout(cujType); FrameTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. if (tracker == null) return false; - tracker.end(REASON_END_NORMAL); - removeTracker(cujType); + // if the end call doesn't return true, another thread is handling end of the cuj. + if (tracker.end(REASON_END_NORMAL)) { + removeTracker(cujType); + } return true; } } @@ -499,33 +529,37 @@ public class InteractionJankMonitor { return cancel(cujType, REASON_CANCEL_NORMAL); } - boolean cancel(@CujType int cujType, @Reasons int reason) { - synchronized (this) { + /** + * Cancels the trace session. + * + * @return boolean true if the tracker is cancelled successfully, false otherwise. + */ + @VisibleForTesting + public boolean cancel(@CujType int cujType, @Reasons int reason) { + synchronized (mLock) { // remove the timeout action first. removeTimeout(cujType); FrameTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. if (tracker == null) return false; - tracker.cancel(reason); - removeTracker(cujType); + // if the cancel call doesn't return true, another thread is handling cancel of the cuj. + if (tracker.cancel(reason)) { + removeTracker(cujType); + } return true; } } private FrameTracker getTracker(@CujType int cuj) { - synchronized (this) { - return mRunningTrackers.get(cuj); - } + return mRunningTrackers.get(cuj); } private void removeTracker(@CujType int cuj) { - synchronized (this) { - mRunningTrackers.remove(cuj); - } + mRunningTrackers.remove(cuj); } private void updateProperties(DeviceConfig.Properties properties) { - synchronized (this) { + synchronized (mLock) { mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY, DEFAULT_SAMPLING_INTERVAL); mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED); @@ -547,10 +581,8 @@ public class InteractionJankMonitor { */ @VisibleForTesting public void trigger(Session session) { - synchronized (this) { - mWorker.getThreadHandler().post( - () -> PerfettoTrigger.trigger(session.getPerfettoTrigger())); - } + mWorker.getThreadHandler().post( + () -> PerfettoTrigger.trigger(session.getPerfettoTrigger())); } /** @@ -648,6 +680,10 @@ public class InteractionJankMonitor { return "WALLPAPER_TRANSITION"; case CUJ_USER_SWITCH: return "USER_SWITCH"; + case CUJ_SPLASHSCREEN_AVD: + return "SPLASHSCREEN_AVD"; + case CUJ_SPLASHSCREEN_EXIT_ANIM: + return "SPLASHSCREEN_EXIT_ANIM"; } return "UNKNOWN"; } diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index d4ae6d769cf7..c1587eb2c4f4 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -112,11 +112,6 @@ static void nativeUpdate(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceCont transaction); } -static void nativeFlushShadowQueue(JNIEnv* env, jclass clazz, jlong ptr) { - sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); - queue->flushShadowQueue(); -} - static void nativeMergeWithNextTransaction(JNIEnv*, jclass clazz, jlong ptr, jlong transactionPtr, jlong framenumber) { sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); @@ -147,7 +142,6 @@ static const JNINativeMethod gMethods[] = { {"nativeDestroy", "(J)V", (void*)nativeDestroy}, {"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction}, {"nativeUpdate", "(JJJJIJ)V", (void*)nativeUpdate}, - {"nativeFlushShadowQueue", "(J)V", (void*)nativeFlushShadowQueue}, {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction}, {"nativeSetTransactionCompleteCallback", "(JJLandroid/graphics/BLASTBufferQueue$TransactionCompleteCallback;)V", diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml index 59e56af01b8d..6869c5fb7935 100644 --- a/core/res/res/layout/alert_dialog.xml +++ b/core/res/res/layout/alert_dialog.xml @@ -124,21 +124,21 @@ android:layout_width="0dip" android:layout_gravity="start" android:layout_weight="1" - style="?android:attr/buttonBarButtonStyle" + style="?android:attr/buttonBarPositiveButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <Button android:id="@+id/button3" android:layout_width="0dip" android:layout_gravity="center_horizontal" android:layout_weight="1" - style="?android:attr/buttonBarButtonStyle" + style="?android:attr/buttonBarNeutralButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <Button android:id="@+id/button2" android:layout_width="0dip" android:layout_gravity="end" android:layout_weight="1" - style="?android:attr/buttonBarButtonStyle" + style="?android:attr/buttonBarNegativeButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <LinearLayout android:id="@+id/rightSpacer" diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index f9786ed3d3e0..25a0ec834316 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -236,7 +236,7 @@ <string name="shutdown_confirm" product="default" msgid="136816458966692315">"আপোনাৰ ফ\'নটো বন্ধ হ\'ব।"</string> <string name="shutdown_confirm_question" msgid="796151167261608447">"আপুনি ফ\'নটোৰ পাৱাৰ অফ কৰিব বিচাৰেনে?"</string> <string name="reboot_safemode_title" msgid="5853949122655346734">"সুৰক্ষিত ম\'ডলৈ ৰিবুট কৰক"</string> - <string name="reboot_safemode_confirm" msgid="1658357874737219624">"আপুনি সুৰক্ষিত ম\'ডলৈ ৰিবুট কৰিব বিচাৰেনে? এই কার্যই আপুনি ইনষ্টল কৰা তৃতীয় পক্ষৰ সকলো এপ্লিকেশ্বন অক্ষম কৰিব। আপুনি পুনৰ ৰিবুট কৰিলে সেইবোৰ পুনঃস্থাপন কৰা হ\'ব।"</string> + <string name="reboot_safemode_confirm" msgid="1658357874737219624">"আপুনি সুৰক্ষিত ম\'ডলৈ ৰিবুট কৰিব বিচাৰেনে? এই কার্যই আপুনি ইনষ্টল কৰা তৃতীয় পক্ষৰ আটাইবোৰ এপ্লিকেশ্বন অক্ষম কৰিব। আপুনি পুনৰ ৰিবুট কৰিলে সেইবোৰ পুনঃস্থাপন কৰা হ\'ব।"</string> <string name="recent_tasks_title" msgid="8183172372995396653">"শেহতীয়া"</string> <string name="no_recent_tasks" msgid="9063946524312275906">"কোনো শেহতীয়া এপ্ নাই।"</string> <string name="global_actions" product="tablet" msgid="4412132498517933867">"টে\'বলেটৰ বিকল্পসমূহ"</string> @@ -255,7 +255,7 @@ <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"ইণ্টাৰেক্টিভ অভিযোগ"</string> <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"বেছিভাগ পৰিস্থিতিত এয়া ব্যৱহাৰ কৰক। ই আপোনাক অভিযোগৰ অগ্ৰগতি ট্ৰেক কৰিবলৈ, সমস্যাটোৰ সম্পর্কে অধিক বিৱৰণ দিবলৈ আৰু স্ক্ৰীণশ্বট ল\'বলৈ অনুমতি দিয়ে। ই কম ব্যৱহাৰ হোৱা সেই শাখাসমূহক অন্তৰ্ভুক্ত নকৰিব পাৰে যিবোৰক অভিযোগ কৰিবলৈ দীৰ্ঘ সময়ৰ প্ৰয়োজন হয়।"</string> <string name="bugreport_option_full_title" msgid="7681035745950045690">"সম্পূৰ্ণ অভিযোগ"</string> - <string name="bugreport_option_full_summary" msgid="1975130009258435885">"যেতিয়া আপোনাৰ ডিভাইচটোৱে সঁহাৰি নিদিয়া হয় বা ই অতি লেহেমীয়া হৈ পৰে বা যেতিয়া আপোনাক সকলো অভিযোগৰ শাখাৰ প্ৰয়োজন হয় তেতিয়া ছিষ্টেমত কম হস্তক্ষেপৰ বাবে এই বিকল্প ব্যৱহাৰ কৰক। আপোনাক অধিক বিৱৰণ দিবলৈ বা অতিৰিক্ত স্ক্ৰীণশ্বট ল’বলৈ নিদিয়ে।"</string> + <string name="bugreport_option_full_summary" msgid="1975130009258435885">"যেতিয়া আপোনাৰ ডিভাইচটোৱে সঁহাৰি নিদিয়া হয় বা ই অতি লেহেমীয়া হৈ পৰে বা যেতিয়া আপোনাক আটাইবোৰ অভিযোগৰ শাখাৰ প্ৰয়োজন হয় তেতিয়া ছিষ্টেমত কম হস্তক্ষেপৰ বাবে এই বিকল্প ব্যৱহাৰ কৰক। আপোনাক অধিক বিৱৰণ দিবলৈ বা অতিৰিক্ত স্ক্ৰীণশ্বট ল’বলৈ নিদিয়ে।"</string> <plurals name="bugreport_countdown" formatted="false" msgid="3906120379260059206"> <item quantity="one">ত্ৰুটি সম্পর্কীয় অভিযোগৰ বাবে <xliff:g id="NUMBER_1">%d</xliff:g>ছেকেণ্ডৰ ভিতৰত স্ক্ৰীণশ্বট লোৱা হ\'ব।</item> <item quantity="other">ত্ৰুটি সম্পর্কীয় অভিযোগৰ বাবে <xliff:g id="NUMBER_1">%d</xliff:g>ছেকেণ্ডৰ ভিতৰত স্ক্ৰীণশ্বট লোৱা হ\'ব।</item> @@ -371,9 +371,9 @@ <string name="permlab_sendSms" msgid="7757368721742014252">"এছএমএছ ৰ বার্তাবোৰ প্ৰেৰণ কৰিব আৰু চাব পাৰে"</string> <string name="permdesc_sendSms" msgid="6757089798435130769">"এপটোক এছএমএছ বাৰ্তা পঠিয়াবলৈ অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা হ\'ব পাৰে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে বাৰ্তা পঠিয়াই আপোনাৰ পৰা মাচুল কাটিব পাৰে৷"</string> <string name="permlab_readSms" msgid="5164176626258800297">"আপোনাৰ পাঠ বার্তাবোৰ পঢ়ক (এছএমএছ বা এমএমএছ)"</string> - <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"এই এপটোৱে আপোনাৰ টেবলেটটোত সংৰক্ষিত সকলো এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> - <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"এই এপ্টোৱে আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা সকলো এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> - <string name="permdesc_readSms" product="default" msgid="774753371111699782">"এই এপটোৱে আপোনাৰ ফ\'নত সংৰক্ষিত সকলো এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> + <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"এই এপ্টোৱে আপোনাৰ টেবলেটটোত সংৰক্ষিত আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> + <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"এই এপ্টোৱে আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> + <string name="permdesc_readSms" product="default" msgid="774753371111699782">"এই এপ্টোৱে আপোনাৰ ফ\'নত সংৰক্ষিত আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string> <string name="permlab_receiveWapPush" msgid="4223747702856929056">"পাঠ বার্তা (WAP) বোৰ লাভ কৰক"</string> <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"এপটোক WAP বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ এই অনুমতিত আপোনালৈ পঠিওৱা বাৰ্তাবোৰ আপোনাক নেদেখুৱাকৈয়ে নিৰীক্ষণ বা মচাৰ সক্ষমতা অন্তৰ্ভুক্ত থাকে৷"</string> <string name="permlab_getTasks" msgid="7460048811831750262">"চলি থকা এপসমূহ বিচাৰি উলিয়াওক"</string> @@ -427,9 +427,9 @@ <string name="permlab_bodySensors" msgid="3411035315357380862">"শৰীৰৰ ছেন্সৰসমূহ (যেনে হৃদপিণ্ডৰ গতিৰ হাৰ নিৰীক্ষক) ব্যৱহাৰ কৰিব পাৰে"</string> <string name="permdesc_bodySensors" product="default" msgid="2365357960407973997">"আপোনাৰ হৃদস্পন্দনৰ দৰে শাৰীৰিক অৱস্থাক নিৰীক্ষণ কৰা ছেন্সৰৰ পৰা ডেটা লাভ কৰিবলৈ এপক অনুমতি দিয়ে।"</string> <string name="permlab_readCalendar" msgid="6408654259475396200">"কেলেণ্ডাৰৰ কাৰ্যক্ৰম আৰু সবিশেষ পঢ়িব পাৰে"</string> - <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"এই এপটোৱে আপোনাৰ টেবলেটটোত সংৰক্ষিত সকলো কেলেণ্ডাৰ কাৰ্যক্ৰম পঢ়িব পাৰে আৰু আপোনাৰ কেলেণ্ডাৰৰ ডেটা শ্বেয়াৰ বা ছেভ কৰিব পাৰে।"</string> - <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"এই এপ্টোৱে আপোনাৰ Android TV ডিভাইচটোত ষ্ট’ৰ কৰি ৰখা সকলো কেলেণ্ডাৰৰ অনুষ্ঠান পঢ়িব পাৰে আৰু আপোনাৰ কেলেণ্ডাৰৰ ডেটা শ্বেয়াৰ অথবা ছেভ কৰিব পাৰে।"</string> - <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"এই এপটোৱে আপোনাৰ ফ\'নটোত সংৰক্ষিত সকলো কেলেণ্ডাৰ কাৰ্যক্ৰম পঢ়িব পাৰে আৰু আপোনাৰ কেলেণ্ডাৰৰ ডেটা শ্বেয়াৰ বা ছেভ কৰিব পাৰে।"</string> + <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"এই এপ্টোৱে আপোনাৰ টেবলেটটোত সংৰক্ষিত আটাইবোৰ কেলেণ্ডাৰ কাৰ্যক্ৰম পঢ়িব পাৰে আৰু আপোনাৰ কেলেণ্ডাৰৰ ডেটা শ্বেয়াৰ বা ছেভ কৰিব পাৰে।"</string> + <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"এই এপ্টোৱে আপোনাৰ Android TV ডিভাইচটোত ষ্ট’ৰ কৰি ৰখা আটাইবোৰ কেলেণ্ডাৰৰ অনুষ্ঠান পঢ়িব পাৰে আৰু আপোনাৰ কেলেণ্ডাৰৰ ডেটা শ্বেয়াৰ অথবা ছেভ কৰিব পাৰে।"</string> + <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"এই এপ্টোৱে আপোনাৰ ফ\'নটোত সংৰক্ষিত আটাইবোৰ কেলেণ্ডাৰ কাৰ্যক্ৰম পঢ়িব পাৰে আৰু আপোনাৰ কেলেণ্ডাৰৰ ডেটা শ্বেয়াৰ বা ছেভ কৰিব পাৰে।"</string> <string name="permlab_writeCalendar" msgid="6422137308329578076">"গৰাকীয়ে নজনাকৈয়ে কেলেণ্ডাৰৰ কাৰ্যক্ৰম সংশোধন কৰি অতিথিসকললৈ ইমেইল প্ৰেৰণ কৰক"</string> <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"এই এপটোৱে আপোনাৰ টেবলেটত কেলেণ্ডাৰ কাৰ্যক্ৰম যোগ কৰিব, আঁতৰাব বা সলনি কৰিব পাৰে। ই এনে বাৰ্তা পঠিয়াব পাৰে যিবোৰ কেলেণ্ডাৰৰ গৰাকীৰ পৰা অহা যেন লাগিব বা ই গৰাকীক নজনোৱাকৈ কাৰ্যক্ৰম সলনি কৰিব পাৰে৷"</string> <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"এই এপ্টোৱে আপোনাৰ Android TV ডিভাইচত কেলেণ্ডাৰ অনুষ্ঠানবোৰ যোগ দিব, আঁতৰাব অথবা সলনি কৰিব পাৰে। এই এপ্টোৱে এনে বাৰ্তা পঠিয়াব পাৰে যিবোৰ কেলেণ্ডাৰৰ গৰাকীৰ পৰা অহা বুলি প্ৰদর্শিত হ’ব পাৰে অথবা এইটোৱে গৰাকীসকলক নজনোৱাকৈ অনুষ্ঠানবোৰ সলনি কৰিব পাৰে।"</string> @@ -516,9 +516,9 @@ <string name="permlab_changeWifiState" msgid="7947824109713181554">"ৱাই-ফাই সংযোগ কৰক আৰু ইয়াৰ সংযোগ বিচ্ছিন্ন কৰক"</string> <string name="permdesc_changeWifiState" msgid="7170350070554505384">"এপটোক ৱাই-ফাই এক্সেছ পইণ্টলৈ সংযোগ কৰিবলৈ আৰু তাৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ আৰু ৱাই-ফাই নেটৱৰ্কসমূহৰ বাবে ডিভাইচ কনফিগাৰেশ্বনত সাল-সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string> <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"ৱাই-ফাই মাল্টিকাষ্ট প্ৰচাৰৰ অনুমতি দিয়ক"</string> - <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"আপোনাৰ টেবলেটৰ লগতে মাল্টিকাষ্ট ঠিকনাবোৰ ও ব্যৱহাৰ কৰি এপক ৱাই-ফাই নেটৱর্কত থকা সকলো ডিভাইচলৈ পঠোৱা পেকেট প্ৰাপ্ত কৰিবলৈ অনুমতি দিয়ে। এই কার্যই ন\'ন মাল্টিকাষ্ট ম\'ডতকৈ বেটাৰিৰ অধিক চ্চাৰ্জ ব্যৱহাৰ কৰে।"</string> - <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"কেৱল আপোনাৰ Android TV ডিভাইচটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনাবোৰ ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা সকলো ডিভাইচলৈ পঠোৱা পেকেটবোৰ লাভ কৰিবলৈ এপ্টোক অনুমতি দিয়ে। এই কার্যই ন’ন-মাল্টিকাষ্ট ম’ডতকৈ অধিক পাৱাৰ ব্যৱহাৰ কৰে।"</string> - <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"আপোনাৰ ফ\'নৰ লগতে মাল্টিকাষ্ট ঠিকনাবোৰ ও ব্যৱহাৰ কৰি এপক ৱাই-ফাই নেটৱর্কত থকা সকলো ডিভাইচলৈ পঠোৱা পেকেট প্ৰাপ্ত কৰিবলৈ অনুমতি দিয়ে। এই কার্যই ন\'ন মাল্টিকাষ্ট ম\'ডতকৈ বেটাৰিৰ অধিক চ্চাৰ্জ ব্যৱহাৰ কৰে।"</string> + <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"কেৱল আপোনাৰ টেবলেটটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনা ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ পঠিওৱা পেকেট লাভ কৰিবলৈ এপ্টোক অনুমতি দিয়ে। এই কার্যই নন মাল্টিকাষ্ট ম\'ডতকৈ অধিক বেটাৰী ব্যৱহাৰ কৰে।"</string> + <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"কেৱল আপোনাৰ Android TV ডিভাইচটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনাবোৰ ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ পঠিওৱা পেকেট লাভ কৰিবলৈ এপ্টোক অনুমতি দিয়ে। এই কার্যই নন-মাল্টিকাষ্ট ম’ডতকৈ অধিক পাৱাৰ ব্যৱহাৰ কৰে।"</string> + <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"আপোনাৰ ফ\'নৰ লগতে ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ মাল্টিকাষ্ট ঠিকনা ব্যৱহাৰ কৰি পঠিওৱা পেকেট লাভ কৰিবলৈ এপক অনুমতি দিয়ে। এই কার্যই নন-মাল্টিকাষ্ট ম\'ডতকৈ বেটাৰীৰ অধিক চাৰ্জ ব্যৱহাৰ কৰে।"</string> <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"ব্লুটুথ ছেটিং এক্সেছ কৰক"</string> <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"স্থানীয় ব্লুটুথ টে\'বলেট কনফিগাৰ কৰিবলৈ আৰু দূৰৱৰ্তী ডিভাইচসমূহৰ সৈতে যোৰা লগাবলৈ আৰু বিচাৰি উলিয়াবলৈ এপটোক অনুমতি দিয়ে।"</string> <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"এপ্টোক আপোনাৰ Android TV ডিভাইচটোত ব্লুটুথ কনফিগাৰ কৰিবলৈ আৰু ৰিম’ট ডিভাইচসমূহ বিচাৰি উলিয়াবলৈ আৰু পেয়াৰ কৰিবলৈ অনুমতি দিয়ে।"</string> @@ -737,7 +737,7 @@ <string name="policydesc_resetPassword" msgid="4626419138439341851">"স্ক্ৰীন লক সলনি কৰক।"</string> <string name="policylab_forceLock" msgid="7360335502968476434">"স্ক্ৰীনখন লক কৰক"</string> <string name="policydesc_forceLock" msgid="1008844760853899693">"স্ক্ৰীন কেনেকৈ আৰু কেতিয়া লক হয় সেয়া নিয়ন্ত্ৰণ কৰক।"</string> - <string name="policylab_wipeData" msgid="1359485247727537311">"সকলো ডেটা মচক"</string> + <string name="policylab_wipeData" msgid="1359485247727537311">"আটাইবোৰ ডেটা মচক"</string> <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"সতৰ্কবাণী প্ৰেৰণ নকৰাকৈয়ে ফেক্টৰী ডেটা ৰিছেট কৰি টেবলেটৰ ডেটা মচক।"</string> <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"কোনো সতর্কবার্তা নপঠিওৱাকৈ ফেক্টৰী ডেটা ৰিছেট কৰি আপোনাৰ Android TV ডিভাইচৰ ডেটা মচক।"</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"সতৰ্কবাণী প্ৰেৰণ নকৰাকৈয়ে ফেক্টৰী ডেটা ৰিছেট কৰি ফ\'নৰ ডেটা মচক।"</string> @@ -752,7 +752,7 @@ <string name="policylab_encryptedStorage" msgid="9012936958126670110">"সঞ্চয়াগাৰৰ এনক্ৰিপশ্বন ছেট কৰক"</string> <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"সঞ্চয় কৰি ৰখা ডেটাক এনক্ৰিপ্ট কৰাৰ প্ৰয়োজন।"</string> <string name="policylab_disableCamera" msgid="5749486347810162018">"কেমেৰাবোৰ অক্ষম কৰক"</string> - <string name="policydesc_disableCamera" msgid="3204405908799676104">"সকলো ডিভাইচৰ কেমেৰাবোৰ ব্যৱহাৰ কৰাত বাধা দিয়ক।"</string> + <string name="policydesc_disableCamera" msgid="3204405908799676104">"আটাইবোৰ ডিভাইচৰ কেমেৰা ব্যৱহাৰ কৰাত বাধা দিয়ক।"</string> <string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"স্ক্ৰীন লকৰ কিছুমান সুবিধা অক্ষম কৰক"</string> <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"স্ক্ৰীন লকৰ কিছুমান সুবিধা ব্যৱহাৰ হোৱাত বাধা দিয়ক।"</string> <string-array name="phoneTypes"> @@ -889,7 +889,7 @@ <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"শুদ্ধ!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"আকৌ চেষ্টা কৰক"</string> <string name="lockscreen_password_wrong" msgid="8605355913868947490">"আকৌ চেষ্টা কৰক"</string> - <string name="lockscreen_storage_locked" msgid="634993789186443380">"সকলো সুবিধা আৰু ডেটাৰ বাবে আনলক কৰক"</string> + <string name="lockscreen_storage_locked" msgid="634993789186443380">"আটাইবোৰ সুবিধা আৰু ডেটাৰ বাবে আনলক কৰক"</string> <string name="faceunlock_multiple_failures" msgid="681991538434031708">"গৰাকীৰ ফেচ আনলক কৰা সৰ্বাধিক সীমা অতিক্ৰম কৰা হ’ল"</string> <string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"কোনো ছিম কাৰ্ড নাই"</string> <string name="lockscreen_missing_sim_message" product="tablet" msgid="8596805728510570760">"টে\'বলেটত ছিম কার্ড নাই।"</string> @@ -918,9 +918,9 @@ <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পিছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক৷"</string> <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"আপুনি নিজৰ আনলক আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ দিলে। আকৌ <xliff:g id="NUMBER_1">%2$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাক নিজৰ Google ছাইন ইন ব্যৱহাৰ কৰি আপোনাৰ Android TV ডিভাইচটো আনলক কৰিবলৈ কোৱা হ’ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g>ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string> <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পিছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক৷"</string> - <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"আপুনি টে\'বলেটটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে টে\'বলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু সকলো ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string> + <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"আপুনি টে\'বলেটটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে টে\'বলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ সকলো ডেটা হেৰুৱাব।"</string> - <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"আপুনি ফ\'নটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু সকলো ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string> + <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"আপুনি ফ\'নটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string> <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"আপুনি অশুদ্ধভাৱে টে\'বলেটটো আনলক কৰিবলৈ <xliff:g id="NUMBER">%d</xliff:g> বাৰ চেষ্টা কৰিছিল। টে\'বলেটটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string> <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। আপোনাৰ Android TV ডিভাইচটো এতিয়া ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব।"</string> <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"আপুনি অশুদ্ধভাৱে ফ\'নটো আনলক কৰিবলৈ <xliff:g id="NUMBER">%d</xliff:g> বাৰ চেষ্টা কৰিছিল। ফ\'নটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string> @@ -1006,7 +1006,7 @@ <string name="autofill_area" msgid="8289022370678448983">"ক্ষেত্ৰ"</string> <string name="autofill_emirate" msgid="2544082046790551168">"এমিৰেট"</string> <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"আপোনাৰ ৱেব বুকমার্কবোৰ আৰু ইতিহাস পঢ়ক"</string> - <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰে ব্যৱহাৰ কৰা সকলো URLৰ ইতিহাস পঢ়িবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string> + <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰে ব্যৱহাৰ কৰা আটাইবোৰ URLৰ ইতিহাস পঢ়িবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string> <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"আপোনাৰ ৱেব বুকমার্কবোৰ আৰু ইতিহাস লিখক"</string> <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"আপোনাৰ টেবলেটত সঞ্চয় কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string> <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"এপ্টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ ইতিহাস আৰু বুকমার্কবোৰ সংশোধন কৰিবলৈ অনুমতি দিয়ে। ব্ৰাউজাৰ ডাটা মোহাৰিবলৈ অথবা সংশোধন কৰিবলৈ ই এপ্টোক অনুমতি দিব পাৰে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ অথবা ৱেব ব্ৰাউজিঙৰ ক্ষমতা থকা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ কৰা নহ’বও পাৰে।"</string> @@ -1149,7 +1149,7 @@ <string name="Midnight" msgid="8176019203622191377">"মাজনিশা"</string> <string name="elapsed_time_short_format_mm_ss" msgid="8689459651807876423">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="2302144714803345056">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> - <string name="selectAll" msgid="1532369154488982046">"সকলো বাছনি কৰক"</string> + <string name="selectAll" msgid="1532369154488982046">"আটাইবোৰ বাছনি কৰক"</string> <string name="cut" msgid="2561199725874745819">"কাটক"</string> <string name="copy" msgid="5472512047143665218">"প্ৰতিলিপি কৰক"</string> <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"ক্লিপব\'ৰ্ডত প্ৰতিলিপি কৰিব পৰা নগ\'ল"</string> @@ -1610,7 +1610,7 @@ <string name="fingerprints" msgid="148690767172613723">"ফিংগাৰপ্ৰিণ্ট:"</string> <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 ফিংগাৰপ্ৰিণ্ট:"</string> <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 ফিংগাৰপ্ৰিণ্ট:"</string> - <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"সকলো চাওক"</string> + <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"আটাইবোৰ চাওক"</string> <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"কাৰ্যকলাপ বাছনি কৰক"</string> <string name="share_action_provider_share_with" msgid="1904096863622941880">"ইয়াৰ জৰিয়তে শ্বেয়াৰ কৰক"</string> <string name="sending" msgid="206925243621664438">"পঠিয়াই থকা হৈছে…"</string> @@ -1677,9 +1677,9 @@ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> - <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে টেবলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু সকলো ব্যৱহাৰকাৰীৰ ডেটা হেৰুৱাব।"</string> - <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ সকলো ডেটা হেৰুৱাব।"</string> - <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু সকলো ব্যৱহাৰকাৰীৰ ডেটা হেৰুৱাব।"</string> + <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে টেবলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰীৰ ডেটা হেৰুৱাব।"</string> + <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ আটাইবোৰ ডেটা হেৰুৱাব।"</string> + <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু ব্যৱহাৰকাৰীৰ আটাইবোৰ ডেটা হেৰুৱাব।"</string> <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"আপুনি <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। টেবলেটটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string> <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। আপোনাৰ Android TV ডিভাইচটো এতিয়া ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব।"</string> <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"আপুনি <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। ফ\'নটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string> @@ -1973,7 +1973,7 @@ <string name="search_language_hint" msgid="7004225294308793583">"ভাষাৰ নাম লিখক"</string> <string name="language_picker_section_suggested" msgid="6556199184638990447">"প্ৰস্তাৱিত"</string> <string name="language_picker_section_all" msgid="1985809075777564284">"সকলো ভাষা"</string> - <string name="region_picker_section_all" msgid="756441309928774155">"সকলো অঞ্চল"</string> + <string name="region_picker_section_all" msgid="756441309928774155">"আটাইবোৰ অঞ্চল"</string> <string name="locale_search_menu" msgid="6258090710176422934">"সন্ধান কৰক"</string> <string name="app_suspended_title" msgid="888873445010322650">"এপটো নাই"</string> <string name="app_suspended_default_message" msgid="6451215678552004172">"এই মুহূৰ্তত <xliff:g id="APP_NAME_0">%1$s</xliff:g> উপলব্ধ নহয়। ইয়াক <xliff:g id="APP_NAME_1">%2$s</xliff:g>এ পৰিচালনা কৰে।"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 96d0662e729f..78ec66185bcc 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -981,7 +981,7 @@ <string name="js_dialog_title" msgid="7464775045615023241">"\'<xliff:g id="TITLE">%s</xliff:g>\' 페이지 내용:"</string> <string name="js_dialog_title_default" msgid="3769524569903332476">"자바스크립트"</string> <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"탐색 확인"</string> - <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"이 페이지 닫기"</string> + <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"이 페이지 나가기"</string> <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"이 페이지에 머무르기"</string> <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\n다른 페이지로 이동하시겠습니까?"</string> <string name="save_password_label" msgid="9161712335355510035">"확인"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 10fd2e131622..886f1eecb03d 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -48,7 +48,7 @@ <string name="invalidPin" msgid="7542498253319440408">"နံပါတ်(၄)ခုမှ(၈)ခုအထိပါရှိသော ပင်နံပါတ်အားထည့်ပါ"</string> <string name="invalidPuk" msgid="8831151490931907083">"နံပါတ်(၈)ခုသို့မဟုတ် ထိုထက်ရှည်သောသော PUKအားထည့်သွင်းပါ"</string> <string name="needPuk" msgid="7321876090152422918">"ဆင်းမ်ကတ် ရဲ့ ပင်နံပါတ် ပြန်ဖွင့်သည့် ကုဒ် သော့ကျနေပါသည်။ ဖွင့်ရန် ကုဒ်အားထည့်သွင်းပါ။"</string> - <string name="needPuk2" msgid="7032612093451537186">"ဆင်းမ်ကဒ်အားမပိတ်ရန် PUK2အားထည့်သွင်းပါ"</string> + <string name="needPuk2" msgid="7032612093451537186">"ဆင်းမ်ကတ်အားမပိတ်ရန် PUK2 အားထည့်သွင်းပါ"</string> <string name="enablePin" msgid="2543771964137091212">"မအောင်မြင်ပါ, SIM/RUIM သော့ကို အရင် သုံးခွင့်ပြုရန်"</string> <plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584"> <item quantity="other">ဆင်းမ်ကတ် သော့မချခင် သင့်တွင် <xliff:g id="NUMBER_1">%d</xliff:g> ခါ ကြိုးစားခွင့်များကျန်ပါသေးသည်။</item> @@ -331,7 +331,7 @@ <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"တို့ထိခြင်းဖြင့် ရှာဖွေမှုကို ဖွင့်ရန်"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"တို့လိုက်သည့်အရာများကို အသံထွက်ဖတ်ပေးပါလိမ့်မည်။ လက်ဟန်အမူအရာများကို အသုံးပြု၍ မျက်နှာပြင်ကို လေ့လာနိုင်ပါသည်။"</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"ရိုက်သောစာများကို စောင့်ကြည့်ရန်"</string> - <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"ကိုယ်ရေးအချက်အလက်များဖြစ်သော ခရက်ဒစ်ကဒ်နံပါတ်နှင့် စကားဝှက်များ ပါဝင်သည်။"</string> + <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"ကိုယ်ရေးအချက်အလက်များဖြစ်သော ခရက်ဒစ်ကတ်နံပါတ်နှင့် စကားဝှက်များ ပါဝင်သည်။"</string> <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"မျက်နှာပြင် ချဲ့ခြင်းကို ထိန်းချုပ်ရန်"</string> <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"မျက်နှာပြင် ဇူးမ်အရွယ်နှင့် နေရာချထားခြင်းကို ထိန်းချုပ်ပါသည်။"</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"လက်ဟန်များ အသုံးပြုပါ"</string> @@ -891,13 +891,13 @@ <string name="lockscreen_password_wrong" msgid="8605355913868947490">"ထပ် စမ်းပါ"</string> <string name="lockscreen_storage_locked" msgid="634993789186443380">"ဝန်ဆောင်မှုနှင့် ဒေတာအားလုံးအတွက် လော့ခ်ဖွင့်ပါ"</string> <string name="faceunlock_multiple_failures" msgid="681991538434031708">"မျက်မှာပြ လော့ခ်ဖွင့်ခြင်း ခွင့်ပြုသော အကြိမ်ရေထက် ကျော်လွန်သွားပါပြီ"</string> - <string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"ဆင်းကဒ် မရှိပါ"</string> - <string name="lockscreen_missing_sim_message" product="tablet" msgid="8596805728510570760">"တက်ပလက်ထဲတွင်း ဆင်းကဒ် မရှိပါ"</string> + <string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"ဆင်းမ်ကတ် မရှိပါ"</string> + <string name="lockscreen_missing_sim_message" product="tablet" msgid="8596805728510570760">"တက်ဘလက်ထဲတွင်း ဆင်းမ်ကတ်မရှိပါ"</string> <string name="lockscreen_missing_sim_message" product="tv" msgid="2582768023352171073">"သင့် Android TV စက်ပစ္စည်းပေါ်တွင် ဆင်းမ်ကတ်မရှိပါ။"</string> - <string name="lockscreen_missing_sim_message" product="default" msgid="1408695081255172556">"ဖုန်းထဲတွင် ဆင်းကဒ် မရှိပါ"</string> + <string name="lockscreen_missing_sim_message" product="default" msgid="1408695081255172556">"ဖုန်းထဲတွင် ဆင်းကတ်မရှိပါ"</string> <string name="lockscreen_missing_sim_instructions" msgid="8473601862688263903">"ဆင်းမ်ကတ် ထည့်ပါ"</string> <string name="lockscreen_missing_sim_instructions_long" msgid="3664999892038416334">"ဆင်းမ်ကတ် မရှိဘူး သို့မဟုတ် ဖတ်မရပါ။ ဆင်းမ်ကတ် တစ်ခုကို ထည့်ပါ။"</string> - <string name="lockscreen_permanent_disabled_sim_message_short" msgid="3812893366715730539">"သုံးစွဲ မရတော့သော ဆင်းကဒ်"</string> + <string name="lockscreen_permanent_disabled_sim_message_short" msgid="3812893366715730539">"သုံး၍ မရတော့သော ဆင်းမ်ကတ်"</string> <string name="lockscreen_permanent_disabled_sim_instructions" msgid="4358929052509450807">"သင့် ဆင်းမ်ကတ်ကို ထာဝရ ပိတ်လိုက်ပါပြီ။\n နောက် ဆင်းမ်ကတ် တစ်ခု အတွက် သင်၏ ကြိုးမဲ့ ဝန်ဆောင်မှု စီမံပေးသူကို ဆက်သွယ်ပါ"</string> <string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"ယခင် တစ်ပုဒ်"</string> <string name="lockscreen_transport_next_description" msgid="2931509904881099919">"နောက် တစ်ပုဒ်"</string> @@ -1340,8 +1340,8 @@ <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"နောင်တွင် ဆက်တင် > အပလီကေးရှင်းများ မှပြောင်းနိုင်သည်"</string> <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"အမြဲခွင့်ပြုရန်"</string> <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"ဘယ်တော့မှခွင့်မပြုပါ"</string> - <string name="sim_removed_title" msgid="5387212933992546283">"SIMကဒ်ဖယ်ရှားခြင်း"</string> - <string name="sim_removed_message" msgid="9051174064474904617">"သတ်မှတ်ထားသောဆင်းမ်ကဒ်ဖြင့် ပြန်လည်ဖွင့်သည့်အထိ မိုဘိုင်းကွန်ယက်ရရှိမည်မဟုတ်ပါ"</string> + <string name="sim_removed_title" msgid="5387212933992546283">"SIM ကတ်ဖယ်ရှားခြင်း"</string> + <string name="sim_removed_message" msgid="9051174064474904617">"သတ်မှတ်ထားသောဆင်းမ်ကတ်ဖြင့် ပြန်လည်ဖွင့်သည့်အထိ မိုဘိုင်းကွန်ရက်ရရှိမည်မဟုတ်ပါ"</string> <string name="sim_done_button" msgid="6464250841528410598">"ပြီးပါပြီ"</string> <string name="sim_added_title" msgid="7930779986759414595">"ဆင်းမ်ကတ် ထည့်ပါသည်"</string> <string name="sim_added_message" msgid="6602906609509958680">"မိုးဘိုင်းကွန်ရက်ကို ဆက်သွယ်ရန် စက်ကို ပြန် စ ပါ"</string> @@ -2046,7 +2046,7 @@ <string name="autofill_continue_yes" msgid="7914985605534510385">"ရှေ့ဆက်ရန်"</string> <string name="autofill_save_type_password" msgid="5624528786144539944">"စကားဝှက်"</string> <string name="autofill_save_type_address" msgid="3111006395818252885">"လိပ်စာ"</string> - <string name="autofill_save_type_credit_card" msgid="3583795235862046693">"ခရက်တစ်ကတ်"</string> + <string name="autofill_save_type_credit_card" msgid="3583795235862046693">"ခရက်ဒစ်ကတ်"</string> <string name="autofill_save_type_debit_card" msgid="3169397504133097468">"ဒက်ဘစ် ကတ်"</string> <string name="autofill_save_type_payment_card" msgid="6555012156728690856">"ငွေပေးချေမှုကတ်"</string> <string name="autofill_save_type_generic_card" msgid="1019367283921448608">"ကတ်"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 70e0d1f37e63..9b6129f7cf61 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -226,9 +226,9 @@ <string name="reboot_to_update_title" msgid="2125818841916373708">"Android ਸਿਸਟਮ ਅੱਪਡੇਟ"</string> <string name="reboot_to_update_prepare" msgid="6978842143587422365">"ਅੱਪਡੇਟ ਦੀ ਤਿਆਰੀ ਕਰ ਰਿਹਾ ਹੈ…"</string> <string name="reboot_to_update_package" msgid="4644104795527534811">"ਅੱਪਡੇਟ ਪੈਕੇਜ ਦੀ ਕਾਰਵਾਈ ਕਰ ਰਿਹਾ ਹੈ..."</string> - <string name="reboot_to_update_reboot" msgid="4474726009984452312">"ਰੀਸਟਾਰਟ ਹੋ ਰਿਹਾ ਹੈ…"</string> + <string name="reboot_to_update_reboot" msgid="4474726009984452312">"ਮੁੜ-ਸ਼ੁਰੂ ਹੋ ਰਿਹਾ ਹੈ…"</string> <string name="reboot_to_reset_title" msgid="2226229680017882787">"ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ"</string> - <string name="reboot_to_reset_message" msgid="3347690497972074356">"ਰੀਸਟਾਰਟ ਹੋ ਰਿਹਾ ਹੈ…"</string> + <string name="reboot_to_reset_message" msgid="3347690497972074356">"ਮੁੜ-ਸ਼ੁਰੂ ਹੋ ਰਿਹਾ ਹੈ…"</string> <string name="shutdown_progress" msgid="5017145516412657345">"ਬੰਦ ਹੋ ਰਿਹਾ ਹੈ…"</string> <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"ਤੁਹਾਡਾ ਟੈਬਲੈੱਟ ਬੰਦ ਕੀਤਾ ਜਾਵੇਗਾ।"</string> <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"ਤੁਹਾਡਾ Android TV ਡੀਵਾਈਸ ਜਲਦ ਬੰਦ ਕੀਤਾ ਜਾਵੇਗਾ।"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 15ec82c380b7..4ae1a17d9922 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1018,7 +1018,7 @@ <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"sửa đổi các quyền về vị trí địa lý của Trình duyệt"</string> <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Cho phép ứng dụng sửa đổi cấp phép vị trí địa lý của Trình duyệt. Ứng dụng độc hại có thể lợi dụng quyền này để cho phép gửi thông tin vị trí tới các trang web tùy ý."</string> <string name="save_password_message" msgid="2146409467245462965">"Bạn có muốn trình duyệt nhớ mật khẩu này không?"</string> - <string name="save_password_notnow" msgid="2878327088951240061">"Không phải bây giờ"</string> + <string name="save_password_notnow" msgid="2878327088951240061">"Để sau"</string> <string name="save_password_remember" msgid="6490888932657708341">"Nhớ"</string> <string name="save_password_never" msgid="6776808375903410659">"Chưa bao giờ"</string> <string name="open_permission_deny" msgid="5136793905306987251">"Bạn không được phép mở trang này."</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index beecd9350b4c..f621a67a1bf1 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -980,7 +980,7 @@ <string name="factorytest_reboot" msgid="2050147445567257365">"重新開機"</string> <string name="js_dialog_title" msgid="7464775045615023241">"「<xliff:g id="TITLE">%s</xliff:g>」網頁指出:"</string> <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string> - <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"確認瀏覽"</string> + <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"確認離開網頁"</string> <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"離開這一頁"</string> <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"停留在這一頁"</string> <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\n你確定要前往其他網頁瀏覽嗎?"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index ab39152fc10f..5cd5c2604e28 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8717,9 +8717,14 @@ <declare-styleable name="VoiceInteractionService"> <!-- The service that hosts active voice interaction sessions. This is required. --> <attr name="sessionService" format="string" /> - <!-- The service that provides voice recognition. This is required. When the user - selects this voice interaction service, they will also be implicitly selecting - the component here for their recognition service. --> + <!-- The service that provides voice recognition. This is required. On Android 11 and + earlier, this must be a valid RecognitionService. + <p> + From Android 12 onward, this attribute does nothing. However, we still require it to + be set to something to reduce the risk that an app with an unspecified value gets + pushed to older platform versions, where it will cause a boot loop. To make sure + developers don't miss it, the system will reset the current assistant if this isn't + specified.--> <attr name="recognitionService" format="string" /> <attr name="settingsActivity" /> <!-- Flag indicating whether this voice interaction service is capable of handling the diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index d7a5e2613175..0d2d047b7f0b 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -16,6 +16,8 @@ package com.android.internal.jank; +import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; +import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE; @@ -34,6 +36,7 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.os.HandlerThread; +import android.os.SystemClock; import android.provider.DeviceConfig; import android.view.View; import android.view.ViewAttachTestActivity; @@ -82,36 +85,23 @@ public class InteractionJankMonitorTest { Handler handler = spy(new Handler(mActivity.getMainLooper())); doReturn(true).when(handler).sendMessageAtTime(any(), anyLong()); - mWorker = spy(new HandlerThread("Interaction-jank-monitor-test")); - doNothing().when(mWorker).start(); + mWorker = mock(HandlerThread.class); doReturn(handler).when(mWorker).getThreadHandler(); } @Test public void testBeginEnd() { - // Should return false if the view is not attached. - InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); - verify(mWorker).start(); - - Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX); - Configuration config = mock(Configuration.class); - when(config.isSurfaceOnly()).thenReturn(false); - FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(), - new ThreadedRendererWrapper(mView.getThreadedRenderer()), - new ViewRootWrapper(mView.getViewRootImpl()), - new SurfaceControlWrapper(), mock(ChoreographerWrapper.class), - new FrameMetricsWrapper(), - /* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1, - /* FrameTrackerListener */ null, config)); + InteractionJankMonitor monitor = createMockedInteractionJankMonitor(); + FrameTracker tracker = createMockedFrameTracker(null); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); - doNothing().when(tracker).triggerPerfetto(); - doNothing().when(tracker).postTraceStartMarker(); + doNothing().when(tracker).begin(); + doReturn(true).when(tracker).end(anyInt()); // Simulate a trace session and see if begin / end are invoked. - assertThat(monitor.begin(mView, session.getCuj())).isTrue(); + assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).begin(); - assertThat(monitor.end(session.getCuj())).isTrue(); - verify(tracker).end(FrameTracker.REASON_END_NORMAL); + assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); + verify(tracker).end(REASON_END_NORMAL); } @Test @@ -140,33 +130,23 @@ public class InteractionJankMonitorTest { } @Test - public void testBeginCancel() { - InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); - + public void testBeginTimeout() { ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); - - Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX); - Configuration config = mock(Configuration.class); - when(config.isSurfaceOnly()).thenReturn(false); - FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(), - new ThreadedRendererWrapper(mView.getThreadedRenderer()), - new ViewRootWrapper(mView.getViewRootImpl()), - new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class), - new FrameMetricsWrapper(), - /* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1, - /* FrameTrackerListener */ null, config)); + InteractionJankMonitor monitor = createMockedInteractionJankMonitor(); + FrameTracker tracker = createMockedFrameTracker(null); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); - doNothing().when(tracker).triggerPerfetto(); - doNothing().when(tracker).postTraceStartMarker(); + doNothing().when(tracker).begin(); + doReturn(true).when(tracker).cancel(anyInt()); - assertThat(monitor.begin(mView, session.getCuj())).isTrue(); + assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).begin(); verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture()); Runnable runnable = captor.getValue(); assertThat(runnable).isNotNull(); mWorker.getThreadHandler().removeCallbacks(runnable); runnable.run(); - verify(tracker).cancel(FrameTracker.REASON_CANCEL_TIMEOUT); + verify(monitor).cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT); + verify(tracker).cancel(REASON_CANCEL_TIMEOUT); } @Test @@ -192,4 +172,43 @@ public class InteractionJankMonitorTest { .isTrue(); } } + + private InteractionJankMonitor createMockedInteractionJankMonitor() { + InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); + doReturn(true).when(monitor).shouldMonitor(anyInt()); + doNothing().when(monitor).notifyEvents(any(), any(), any()); + return monitor; + } + + private FrameTracker createMockedFrameTracker(FrameTracker.FrameTrackerListener listener) { + Session session = spy(new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX)); + doReturn(false).when(session).logToStatsd(); + + ThreadedRendererWrapper threadedRenderer = mock(ThreadedRendererWrapper.class); + doNothing().when(threadedRenderer).addObserver(any()); + doNothing().when(threadedRenderer).removeObserver(any()); + + ViewRootWrapper viewRoot = spy(new ViewRootWrapper(mView.getViewRootImpl())); + doNothing().when(viewRoot).addSurfaceChangedCallback(any()); + + SurfaceControlWrapper surfaceControl = mock(SurfaceControlWrapper.class); + doNothing().when(surfaceControl).addJankStatsListener(any(), any()); + doNothing().when(surfaceControl).removeJankStatsListener(any()); + + final ChoreographerWrapper choreographer = mock(ChoreographerWrapper.class); + doReturn(SystemClock.elapsedRealtime()).when(choreographer).getVsyncId(); + + Configuration configuration = mock(Configuration.class); + when(configuration.isSurfaceOnly()).thenReturn(false); + + FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(), + threadedRenderer, viewRoot, surfaceControl, choreographer, + new FrameMetricsWrapper(), /* traceThresholdMissedFrames= */ 1, + /* traceThresholdFrameTimeMillis= */ -1, listener, configuration)); + + doNothing().when(tracker).postTraceStartMarker(); + doNothing().when(tracker).triggerPerfetto(); + + return tracker; + } } diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml index ab162dd590a1..2c59c7390ebf 100644 --- a/data/etc/car/com.google.android.car.kitchensink.xml +++ b/data/etc/car/com.google.android.car.kitchensink.xml @@ -67,6 +67,7 @@ <permission name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/> <permission name="android.car.permission.CAR_MILEAGE"/> <permission name="android.car.permission.CAR_MOCK_VEHICLE_HAL"/> + <permission name="android.car.permission.CAR_MONITOR_CLUSTER_NAVIGATION_STATE"/> <permission name="android.car.permission.CAR_NAVIGATION_MANAGER"/> <permission name="android.car.permission.CAR_POWER"/> <permission name="android.car.permission.CAR_PROJECTION"/> diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 36215ecc1403..fca4de91a352 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -34,7 +34,6 @@ public final class BLASTBufferQueue { private static native void nativeSetNextTransaction(long ptr, long transactionPtr); private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height, int format, long transactionPtr); - private static native void nativeFlushShadowQueue(long ptr); private static native void nativeMergeWithNextTransaction(long ptr, long transactionPtr, long frameNumber); private static native void nativeSetTransactionCompleteCallback(long ptr, long frameNumber, @@ -124,10 +123,6 @@ public final class BLASTBufferQueue { } } - public void flushShadowQueue() { - nativeFlushShadowQueue(mNativeObject); - } - /** * Merge the transaction passed in to the next transaction in BlastBufferQueue. The next * transaction will be applied or merged when the next frame with specified frame number diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java index e6ad011e617e..eb9429747b66 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java @@ -30,18 +30,21 @@ import java.util.regex.Pattern; /** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ final class CommonDisplayFeature implements DisplayFeature { private static final Pattern FEATURE_PATTERN = - Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); + Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?"); private static final String FEATURE_TYPE_FOLD = "fold"; private static final String FEATURE_TYPE_HINGE = "hinge"; + private static final String PATTERN_STATE_FLAT = "flat"; + private static final String PATTERN_STATE_HALF_OPENED = "half-opened"; + // TODO(b/183049815): Support feature strings that include the state of the feature. + /** * Parses a display feature from a string. * * @throws IllegalArgumentException if the provided string is improperly formatted or could not - * otherwise be parsed. - * + * otherwise be parsed. * @see #FEATURE_PATTERN */ @NonNull @@ -52,6 +55,7 @@ final class CommonDisplayFeature implements DisplayFeature { } try { String featureType = featureMatcher.group(1); + featureType = featureType == null ? "" : featureType; int type; switch (featureType) { case FEATURE_TYPE_FOLD: @@ -73,8 +77,21 @@ final class CommonDisplayFeature implements DisplayFeature { if (isZero(featureRect)) { throw new IllegalArgumentException("Feature has empty bounds: " + string); } - - return new CommonDisplayFeature(type, null, featureRect); + String stateString = featureMatcher.group(6); + stateString = stateString == null ? "" : stateString; + Integer state; + switch (stateString) { + case PATTERN_STATE_FLAT: + state = COMMON_STATE_FLAT; + break; + case PATTERN_STATE_HALF_OPENED: + state = COMMON_STATE_HALF_OPENED; + break; + default: + state = null; + break; + } + return new CommonDisplayFeature(type, state, featureRect); } catch (NumberFormatException e) { throw new IllegalArgumentException("Malformed feature description: " + string, e); } @@ -87,6 +104,7 @@ final class CommonDisplayFeature implements DisplayFeature { private final Rect mRect; CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) { + assertValidState(state); this.mType = type; this.mState = state; if (rect.width() == 0 && rect.height() == 0) { @@ -125,4 +143,11 @@ final class CommonDisplayFeature implements DisplayFeature { public int hashCode() { return Objects.hash(mType, mState, mRect); } + + private static void assertValidState(@Nullable Integer state) { + if (state != null && state != COMMON_STATE_FLAT && state != COMMON_STATE_HALF_OPENED) { + throw new IllegalArgumentException("Invalid state: " + state + + "must be either COMMON_STATE_FLAT or COMMON_STATE_HALF_OPENED"); + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java index b6c4c436d0b1..573641857b99 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java @@ -16,11 +16,15 @@ package androidx.window.common; +import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Rect; import androidx.annotation.NonNull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ public interface DisplayFeature { /** Returns the type of the feature. */ @@ -28,9 +32,29 @@ public interface DisplayFeature { /** Returns the state of the feature, or {@code null} if the feature has no state. */ @Nullable + @State Integer getState(); /** Returns the bounds of the feature. */ @NonNull Rect getRect(); + + /** + * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar + * and Extensions do not match exactly. + */ + int COMMON_STATE_FLAT = 3; + /** + * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in + * Sidecar and Extensions do not match exactly. + */ + int COMMON_STATE_HALF_OPENED = 2; + + /** + * The possible states for a folding hinge. + */ + @IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED}) + @Retention(RetentionPolicy.SOURCE) + @interface State {} + } 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 42b438041d7a..20515e71a91b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -516,12 +516,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Skipping containers that do not have any activities to report. continue; } - ActivityStack primaryContainer = - new ActivityStack( - container.getPrimaryContainer().collectActivities()); - ActivityStack secondaryContainer = - new ActivityStack( - container.getSecondaryContainer().collectActivities()); + ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack(); + ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack(); SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer, // Splits that are not showing side-by-side are reported as having 0 split 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 54e44a70ed40..80d9c2c1719c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -109,6 +109,10 @@ class TaskFragmentContainer { return allActivities; } + ActivityStack toActivityStack() { + return new ActivityStack(collectActivities(), mInfo.getRunningActivityCount() == 0); + } + void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { mPendingAppearedActivities.add(pendingAppearedActivity); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index 383d91da6af8..32d447ef1586 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -122,8 +122,19 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private int getFeatureState(DisplayFeature feature) { Integer featureState = feature.getState(); Optional<Integer> posture = mDevicePostureProducer.getData(); - int fallbackPosture = posture.orElse(FoldingFeature.STATE_FLAT); - return featureState == null ? fallbackPosture : featureState; + int fallbackPosture = posture.orElse(DisplayFeature.COMMON_STATE_FLAT); + int displayFeatureState = featureState == null ? fallbackPosture : featureState; + return convertToExtensionState(displayFeatureState); + } + + private int convertToExtensionState(int state) { + switch (state) { + case DisplayFeature.COMMON_STATE_FLAT: + return FoldingFeature.STATE_FLAT; + case DisplayFeature.COMMON_STATE_HALF_OPENED: + return FoldingFeature.STATE_HALF_OPENED; + } + return FoldingFeature.STATE_FLAT; } private void onDisplayFeaturesChanged() { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index ece198cad818..aa949f126154 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -38,6 +38,7 @@ import androidx.window.util.DataProducer; import androidx.window.util.PriorityDataProducer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -47,6 +48,7 @@ import java.util.Optional; */ class SampleSidecarImpl extends StubSidecar { private static final String TAG = "SampleSidecar"; + private static final boolean DEBUG = false; private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; private final DataProducer<Integer> mDevicePostureProducer; @@ -88,10 +90,30 @@ class SampleSidecarImpl extends StubSidecar { Optional<Integer> posture = mDevicePostureProducer.getData(); SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = posture.orElse(SidecarDeviceState.POSTURE_UNKNOWN); + deviceState.posture = posture.orElse(deviceStateFromFeature()); return deviceState; } + private int deviceStateFromFeature() { + List<DisplayFeature> storedFeatures = mDisplayFeatureProducer.getData() + .orElse(Collections.emptyList()); + for (int i = 0; i < storedFeatures.size(); i++) { + DisplayFeature feature = storedFeatures.get(i); + final int state = feature.getState() == null ? -1 : feature.getState(); + if (DEBUG && feature.getState() == null) { + Log.d(TAG, "feature#getState was null for DisplayFeature: " + feature); + } + + switch (state) { + case DisplayFeature.COMMON_STATE_FLAT: + return SidecarDeviceState.POSTURE_OPENED; + case DisplayFeature.COMMON_STATE_HALF_OPENED: + return SidecarDeviceState.POSTURE_HALF_OPENED; + } + } + return SidecarDeviceState.POSTURE_UNKNOWN; + } + @NonNull @Override public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 4f36c9c690c9..830d13dd6dc5 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 3ba1a34bd432..73c2e8bfa78f 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -38,6 +38,7 @@ filegroup { path: "src", } +// Sources that have no dependencies that can be used directly downstream of this library filegroup { name: "wm_shell_util-sources", srcs: [ @@ -46,6 +47,7 @@ filegroup { path: "src", } +// Aidls which can be used directly downstream of this library filegroup { name: "wm_shell-aidls", srcs: [ diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml new file mode 100644 index 000000000000..94165a11eccb --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@color/size_compat_hint_bubble"/> + <corners android:radius="@dimen/size_compat_hint_corner_radius"/> +</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml new file mode 100644 index 000000000000..a8f0f76ef27f --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="@dimen/size_compat_hint_point_width" + android:height="8dp" + android:viewportWidth="10" + android:viewportHeight="8"> + <path + android:fillColor="@color/size_compat_hint_bubble" + android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml index 73a48d31a814..3e486df71f91 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml +++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml @@ -15,14 +15,21 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> <path - android:fillColor="#aa000000" - android:pathData="M0,12 a12,12 0 1,0 24,0 a12,12 0 1,0 -24,0" /> - <path - android:fillColor="@android:color/white" - android:pathData="M17.65,6.35c-1.63,-1.63 -3.94,-2.57 -6.48,-2.31c-3.67,0.37 -6.69,3.35 -7.1,7.02C3.52,15.91 7.27,20 12,20c3.19,0 5.93,-1.87 7.21,-4.57c0.31,-0.66 -0.16,-1.43 -0.89,-1.43h-0.01c-0.37,0 -0.72,0.2 -0.88,0.53c-1.13,2.43 -3.84,3.97 -6.81,3.32c-2.22,-0.49 -4.01,-2.3 -4.49,-4.52C5.31,9.44 8.26,6 12,6c1.66,0 3.14,0.69 4.22,1.78l-2.37,2.37C13.54,10.46 13.76,11 14.21,11H19c0.55,0 1,-0.45 1,-1V5.21c0,-0.45 -0.54,-0.67 -0.85,-0.35L17.65,6.35z"/> + android:fillColor="#53534D" + android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" /> + <group + android:translateX="12" + android:translateY="12"> + <path + android:fillColor="#E4E3DA" + android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/> + <path + android:fillColor="#E4E3DA" + android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/> + </group> </vector> diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml index 544b731bb550..05b15060946d 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml @@ -32,7 +32,7 @@ android:id="@+id/bubble_view_name" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" android:textSize="13sp" - android:layout_width="wrap_content" + android:layout_width="@dimen/bubble_name_width" android:layout_height="wrap_content" android:maxLines="1" android:lines="2" diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml index 0dea87c6b7fc..17347f627049 100644 --- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml +++ b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml @@ -22,41 +22,34 @@ <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="center" android:clipToPadding="false" - android:padding="@dimen/bubble_elevation"> + android:paddingBottom="5dp"> <LinearLayout + android:id="@+id/size_compat_hint_popup" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@android:color/background_light" - android:elevation="@dimen/bubble_elevation" - android:orientation="vertical"> + android:orientation="vertical" + android:clickable="true"> <TextView - android:layout_width="180dp" + android:layout_width="188dp" android:layout_height="wrap_content" - android:paddingLeft="10dp" - android:paddingRight="10dp" - android:paddingTop="10dp" + android:lineSpacingExtra="4sp" + android:background="@drawable/size_compat_hint_bubble" + android:padding="16dp" android:text="@string/restart_button_description" android:textAlignment="viewStart" - android:textColor="@android:color/primary_text_light" - android:textSize="16sp"/> + android:textColor="#E4E3DA" + android:textSize="14sp"/> - <Button - android:id="@+id/got_it" + <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" - android:includeFontPadding="false" android:layout_gravity="end" - android:minHeight="36dp" - android:background="?android:attr/selectableItemBackground" - android:text="@string/got_it" - android:textAllCaps="true" - android:textColor="#3c78d8" - android:textSize="16sp" - android:textStyle="bold"/> + android:src="@drawable/size_compat_hint_point" + android:paddingHorizontal="@dimen/size_compat_hint_corner_radius" + android:contentDescription="@null"/> </LinearLayout> diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml index cd3153145be3..47e76f061877 100644 --- a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml +++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml @@ -19,12 +19,21 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - <ImageButton - android:id="@+id/size_compat_restart_button" - android:layout_width="@dimen/size_compat_button_size" - android:layout_height="@dimen/size_compat_button_size" - android:layout_gravity="center" - android:src="@drawable/size_compat_restart_button" - android:contentDescription="@string/restart_button_description"/> + <FrameLayout + android:layout_width="@dimen/size_compat_button_width" + android:layout_height="@dimen/size_compat_button_height" + android:clipToPadding="false" + android:paddingBottom="16dp"> + + <ImageButton + android:id="@+id/size_compat_restart_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:src="@drawable/size_compat_restart_button" + android:background="@android:color/transparent" + android:contentDescription="@string/restart_button_description"/> + + </FrameLayout> </com.android.wm.shell.sizecompatui.SizeCompatRestartButton> diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index 93c0352a2ad3..b25a2189cd4d 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -29,6 +29,7 @@ <color name="bubbles_light">#FFFFFF</color> <color name="bubbles_dark">@color/GM2_grey_800</color> <color name="bubbles_icon_tint">@color/GM2_grey_700</color> + <color name="size_compat_hint_bubble">#30312B</color> <!-- GM2 colors --> <color name="GM2_grey_200">#E8EAED</color> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index f85766437b44..e9b9ec3f7d89 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -115,6 +115,8 @@ <dimen name="bubble_spacing">3dp</dimen> <!-- Size of the bubble. --> <dimen name="bubble_size">60dp</dimen> + <!-- Width of bubble name view --> + <dimen name="bubble_name_width">90dp</dimen> <!-- Size of the badge shown on the bubble. --> <dimen name="bubble_badge_size">24dp</dimen> <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. --> @@ -194,8 +196,17 @@ <!-- Size of user education views on large screens (phone is just match parent). --> <dimen name="bubbles_user_education_width_large_screen">400dp</dimen> - <!-- The width/height of the size compat restart button. --> - <dimen name="size_compat_button_size">48dp</dimen> + <!-- The width of the size compat restart button including padding. --> + <dimen name="size_compat_button_width">80dp</dimen> + + <!-- The height of the size compat restart button including padding. --> + <dimen name="size_compat_button_height">64dp</dimen> + + <!-- The radius of the corners of the size compat hint bubble. --> + <dimen name="size_compat_hint_corner_radius">28dp</dimen> + + <!-- The width of the size compat hint point. --> + <dimen name="size_compat_hint_point_width">10dp</dimen> <!-- The width of the brand image on staring surface. --> <dimen name="starting_surface_brand_image_width">200dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index e512698ab66c..764854af3b3f 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -155,7 +155,4 @@ <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] --> <string name="restart_button_description">Tap to restart this app and go full screen.</string> - - <!-- Generic "got it" acceptance of dialog or cling [CHAR LIMIT=NONE] --> - <string name="got_it">Got it</string> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index e87b150a0709..358553d70501 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -24,6 +24,7 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import java.io.PrintWriter; @@ -43,6 +44,7 @@ public final class ShellCommandHandlerImpl { private final Optional<OneHandedController> mOneHandedOptional; private final Optional<HideDisplayCutoutController> mHideDisplayCutout; private final Optional<AppPairsController> mAppPairsOptional; + private final Optional<RecentTasksController> mRecentTasks; private final ShellTaskOrganizer mShellTaskOrganizer; private final ShellExecutor mMainExecutor; private final HandlerImpl mImpl = new HandlerImpl(); @@ -55,8 +57,10 @@ public final class ShellCommandHandlerImpl { Optional<OneHandedController> oneHandedOptional, Optional<HideDisplayCutoutController> hideDisplayCutout, Optional<AppPairsController> appPairsOptional, + Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor) { mShellTaskOrganizer = shellTaskOrganizer; + mRecentTasks = recentTasks; mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mPipOptional = pipOptional; @@ -85,6 +89,9 @@ public final class ShellCommandHandlerImpl { pw.println(); pw.println(); mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, "")); + pw.println(); + pw.println(); + mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, "")); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index fa58fcda3d3b..f5678776ed78 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -29,8 +29,8 @@ 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.fullscreen.FullscreenUnfoldController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.Transitions; @@ -49,7 +49,6 @@ public class ShellInitImpl { private final DragAndDropController mDragAndDropController; private final ShellTaskOrganizer mShellTaskOrganizer; private final Optional<BubbleController> mBubblesOptional; - private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<AppPairsController> mAppPairsOptional; private final Optional<PipTouchHandler> mPipTouchHandlerOptional; @@ -59,6 +58,7 @@ public class ShellInitImpl { private final ShellExecutor mMainExecutor; private final Transitions mTransitions; private final StartingWindowController mStartingWindow; + private final Optional<RecentTasksController> mRecentTasks; private final InitImpl mImpl = new InitImpl(); @@ -69,13 +69,13 @@ public class ShellInitImpl { DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional<BubbleController> bubblesOptional, - Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController, Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional, + Optional<RecentTasksController> recentTasks, Transitions transitions, StartingWindowController startingWindow, ShellExecutor mainExecutor) { @@ -85,13 +85,13 @@ public class ShellInitImpl { mDragAndDropController = dragAndDropController; mShellTaskOrganizer = shellTaskOrganizer; mBubblesOptional = bubblesOptional; - mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mAppPairsOptional = appPairsOptional; mFullscreenTaskListener = fullscreenTaskListener; mPipTouchHandlerOptional = pipTouchHandlerOptional; mFullscreenUnfoldController = fullscreenUnfoldTransitionController; mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f); + mRecentTasks = recentTasks; mTransitions = transitions; mMainExecutor = mainExecutor; mStartingWindow = startingWindow; @@ -135,6 +135,7 @@ public class ShellInitImpl { f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM)); mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init); + mRecentTasks.ifPresent(RecentTasksController::init); } @ExternalThread diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 020ecb7186ed..75bc46125324 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -51,6 +51,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.startingsurface.StartingWindowController; @@ -59,6 +60,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.function.Consumer; /** @@ -150,20 +152,34 @@ public class ShellTaskOrganizer extends TaskOrganizer implements @Nullable private final SizeCompatUIController mSizeCompatUI; + @Nullable + private final Optional<RecentTasksController> mRecentTasks; + public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { - this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */); + this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */, + Optional.empty() /* recentTasksController */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable SizeCompatUIController sizeCompatUI) { - this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI); + this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, + Optional.empty() /* recentTasksController */); + } + + public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable + SizeCompatUIController sizeCompatUI, + Optional<RecentTasksController> recentTasks) { + this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, + recentTasks); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context, @Nullable SizeCompatUIController sizeCompatUI) { + Context context, @Nullable SizeCompatUIController sizeCompatUI, + Optional<RecentTasksController> recentTasks) { super(taskOrganizerController, mainExecutor); mSizeCompatUI = sizeCompatUI; + mRecentTasks = recentTasks; if (sizeCompatUI != null) { sizeCompatUI.setSizeCompatUICallback(this); } @@ -401,6 +417,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements // Notify the size compat UI if the listener or task info changed. notifySizeCompatUI(taskInfo, newListener); } + if (data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode()) { + // Notify the recent tasks when a task changes windowing modes + mRecentTasks.ifPresent(recentTasks -> + recentTasks.onTaskWindowingModeChanged(taskInfo)); + } } } @@ -428,6 +449,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements notifyLocusVisibilityIfNeeded(taskInfo); // Pass null for listener to remove the size compat UI on this task if there is any. notifySizeCompatUI(taskInfo, null /* taskListener */); + // Notify the recent tasks that a task has been removed + mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index b6d65bebff28..f7af4e1dd1d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1028,13 +1028,16 @@ public class BubbleController { // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason. // This means that the app or channel's ability to bubble has been revoked. mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED); - } else if (isActiveBubble && !shouldBubbleUp) { - // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it. - // This happens when DND is enabled and configured to hide bubbles. Dismissing with - // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that - // the bubble will be re-created if shouldBubbleUp returns true. + } else if (isActiveBubble && (!shouldBubbleUp || entry.getRanking().isSuspended())) { + // If this entry is allowed to bubble, but cannot currently bubble up or is + // suspended, dismiss it. This happens when DND is enabled and configured to hide + // bubbles, or focus mode is enabled and the app is designated as distracting. + // Dismissing with the reason DISMISS_NO_BUBBLE_UP will retain the underlying + // notification, so that the bubble will be re-created if shouldBubbleUp returns + // true. mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP); - } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { + } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble + && !entry.getRanking().isSuspended()) { entry.setFlagBubble(true); onEntryUpdated(entry, true /* shouldBubbleUp */); } @@ -1134,7 +1137,8 @@ public class BubbleController { if (reason == DISMISS_USER_CHANGED || reason == DISMISS_NO_BUBBLE_UP) { continue; } - if (reason == DISMISS_NOTIF_CANCEL) { + if (reason == DISMISS_NOTIF_CANCEL + || reason == DISMISS_SHORTCUT_REMOVED) { bubblesToBeRemovedFromRepository.add(bubble); } if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java new file mode 100644 index 000000000000..b77ac8a2b951 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java @@ -0,0 +1,123 @@ +/* + * 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.common; + +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; +import android.util.Slog; + +import androidx.annotation.BinderThread; + +import java.util.function.Consumer; + +/** + * Manages the lifecycle of a single instance of a remote listener, including the clean up if the + * remote process dies. All calls on this class should happen on the main shell thread. + * + * @param <C> The controller (must be RemoteCallable) + * @param <L> The remote listener interface type + */ +public class SingleInstanceRemoteListener<C extends RemoteCallable, L extends IInterface> { + private static final String TAG = SingleInstanceRemoteListener.class.getSimpleName(); + + /** + * Simple callable interface that throws a remote exception. + */ + public interface RemoteCall<L> { + void accept(L l) throws RemoteException; + } + + private final C mCallableController; + private final Consumer<C> mOnRegisterCallback; + private final Consumer<C> mOnUnregisterCallback; + + L mListener; + + private final IBinder.DeathRecipient mListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + final C callableController = mCallableController; + mCallableController.getRemoteCallExecutor().execute(() -> { + mListener = null; + mOnUnregisterCallback.accept(callableController); + }); + } + }; + + /** + * @param onRegisterCallback Callback when register() is called (same thread) + * @param onUnregisterCallback Callback when unregister() is called (same thread as unregister() + * or the callableController.getRemoteCallbackExecutor() thread) + */ + public SingleInstanceRemoteListener(C callableController, + Consumer<C> onRegisterCallback, + Consumer<C> onUnregisterCallback) { + mCallableController = callableController; + mOnRegisterCallback = onRegisterCallback; + mOnUnregisterCallback = onUnregisterCallback; + } + + /** + * Registers this listener, storing a reference to it and calls the provided method in the + * constructor. + */ + public void register(L listener) { + if (mListener != null) { + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, 0 /* flags */); + } + if (listener != null) { + try { + listener.asBinder().linkToDeath(mListenerDeathRecipient, 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } + } + mListener = listener; + mOnRegisterCallback.accept(mCallableController); + } + + /** + * Unregisters this listener, removing all references to it and calls the provided method in the + * constructor. + */ + public void unregister() { + if (mListener != null) { + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, 0 /* flags */); + } + mListener = null; + mOnUnregisterCallback.accept(mCallableController); + } + + /** + * Safely wraps a call to the remote listener. + */ + public void call(RemoteCall<L> handler) { + if (mListener == null) { + Slog.e(TAG, "Failed remote call on null listener"); + return; + } + try { + handler.accept(mListener); + } catch (RemoteException e) { + Slog.e(TAG, "Failed remote call", e); + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java index 6e4b81563441..2a7dd5aeb341 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java @@ -25,6 +25,8 @@ import android.content.ClipDescription; import android.content.pm.ActivityInfo; import android.view.DragEvent; +import androidx.annotation.Nullable; + import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.UiEvent; @@ -61,9 +63,7 @@ public class DragAndDropEventLogger { mInstanceId = mIdSequence.newInstanceId(); } mActivityInfo = item.getActivityInfo(); - mUiEventLogger.logWithInstanceId(getStartEnum(description), - mActivityInfo.applicationInfo.uid, - mActivityInfo.applicationInfo.packageName, mInstanceId); + log(getStartEnum(description), mActivityInfo); return mInstanceId; } @@ -71,18 +71,21 @@ public class DragAndDropEventLogger { * Logs a successful drop. */ public void logDrop() { - mUiEventLogger.logWithInstanceId(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_DROPPED, - mActivityInfo.applicationInfo.uid, - mActivityInfo.applicationInfo.packageName, mInstanceId); + log(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_DROPPED, mActivityInfo); } /** * Logs the end of a drag. */ public void logEnd() { - mUiEventLogger.logWithInstanceId(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_END, - mActivityInfo.applicationInfo.uid, - mActivityInfo.applicationInfo.packageName, mInstanceId); + log(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_END, mActivityInfo); + } + + private void log(UiEventLogger.UiEventEnum event, @Nullable ActivityInfo activityInfo) { + mUiEventLogger.logWithInstanceId(event, + activityInfo == null ? 0 : activityInfo.applicationInfo.uid, + activityInfo == null ? null : activityInfo.applicationInfo.packageName, + mInstanceId); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 5fb3297aa6d3..8a8d7c68d9f6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -16,6 +16,7 @@ package com.android.wm.shell.freeform; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; @@ -27,6 +28,7 @@ import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; import android.view.SurfaceControl; +import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; @@ -83,6 +85,13 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); return; } + + // Clears windowing mode and window bounds to let the task inherits from its new parent. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(taskInfo.token, null) + .setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + mSyncQueue.queue(wct); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d", taskInfo.taskId); mTasks.remove(taskInfo.taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java index fc1b704e95ad..aa3868cfca84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.fullscreen; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.util.MathUtils.lerp; import static android.view.Display.DEFAULT_DISPLAY; @@ -163,7 +164,10 @@ public final class FullscreenUnfoldController implements UnfoldListener, public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId); if (animationContext != null) { - resetSurface(animationContext); + // PiP task has its own cleanup path, ignore surface reset to avoid conflict. + if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) { + resetSurface(animationContext); + } mAnimationContextByTaskId.remove(taskInfo.taskId); } 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 8e5c5c52cb3f..51eea370b98d 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 @@ -43,7 +43,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Rect; -import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -69,6 +68,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.onehanded.OneHandedController; @@ -117,13 +117,28 @@ public class PipController implements PipTransitionController.PipTransitionCallb private final Rect mTmpInsetBounds = new Rect(); private boolean mIsInFixedRotation; - private IPipAnimationListener mPinnedStackAnimationRecentsCallback; + private PipAnimationListener mPinnedStackAnimationRecentsCallback; protected PhonePipMenuController mMenuController; protected PipTaskOrganizer mPipTaskOrganizer; protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener = new PipControllerPinnedTaskListener(); + private interface PipAnimationListener { + /** + * Notifies the listener that the Pip animation is started. + */ + void onPipAnimationStarted(); + + /** + * Notifies the listener about PiP round corner radius changes. + * Listener can expect an immediate callback the first time they attach. + * + * @param cornerRadius the pixel value of the corner radius, zero means it's disabled. + */ + void onPipCornerRadiusChanged(int cornerRadius); + } + /** * Handler for display rotation changes. */ @@ -551,7 +566,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb animationType == PipAnimationController.ANIM_TYPE_BOUNDS); } - private void setPinnedStackAnimationListener(IPipAnimationListener callback) { + private void setPinnedStackAnimationListener(PipAnimationListener callback) { mPinnedStackAnimationRecentsCallback = callback; onPipCornerRadiusChanged(); } @@ -560,11 +575,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb if (mPinnedStackAnimationRecentsCallback != null) { final int cornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius); - try { - mPinnedStackAnimationRecentsCallback.onPipCornerRadiusChanged(cornerRadius); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call onPipCornerRadiusChanged", e); - } + mPinnedStackAnimationRecentsCallback.onPipCornerRadiusChanged(cornerRadius); } } @@ -623,11 +634,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Disable touches while the animation is running mTouchHandler.setTouchEnabled(false); if (mPinnedStackAnimationRecentsCallback != null) { - try { - mPinnedStackAnimationRecentsCallback.onPipAnimationStarted(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call onPinnedStackAnimationStarted()", e); - } + mPinnedStackAnimationRecentsCallback.onPipAnimationStarted(); } } @@ -866,22 +873,25 @@ public class PipController implements PipTransitionController.PipTransitionCallb @BinderThread private static class IPipImpl extends IPip.Stub { private PipController mController; - private IPipAnimationListener mListener; - private final IBinder.DeathRecipient mListenerDeathRecipient = - new IBinder.DeathRecipient() { - @Override - @BinderThread - public void binderDied() { - final PipController controller = mController; - controller.getRemoteCallExecutor().execute(() -> { - mListener = null; - controller.setPinnedStackAnimationListener(null); - }); - } - }; + private final SingleInstanceRemoteListener<PipController, + IPipAnimationListener> mListener; + private final PipAnimationListener mPipAnimationListener = new PipAnimationListener() { + @Override + public void onPipAnimationStarted() { + mListener.call(l -> l.onPipAnimationStarted()); + } + + @Override + public void onPipCornerRadiusChanged(int cornerRadius) { + mListener.call(l -> l.onPipCornerRadiusChanged(cornerRadius)); + } + }; IPipImpl(PipController controller) { mController = controller; + mListener = new SingleInstanceRemoteListener<>(mController, + c -> c.setPinnedStackAnimationListener(mPipAnimationListener), + c -> c.setPinnedStackAnimationListener(null)); } /** @@ -925,23 +935,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb public void setPinnedStackAnimationListener(IPipAnimationListener listener) { executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener", (controller) -> { - if (mListener != null) { - // Reset the old death recipient - mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } if (listener != null) { - // Register the death recipient for the new listener to clear the listener - try { - listener.asBinder().linkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to link to death"); - return; - } + mListener.register(listener); + } else { + mListener.unregister(); } - mListener = listener; - controller.setPinnedStackAnimationListener(listener); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl new file mode 100644 index 000000000000..6e78fcba4a00 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -0,0 +1,41 @@ +/* + * 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.recents; + +import com.android.wm.shell.recents.IRecentTasksListener; +import com.android.wm.shell.util.GroupedRecentTaskInfo; + +/** + * Interface that is exposed to remote callers to fetch recent tasks. + */ +interface IRecentTasks { + + /** + * Registers a recent tasks listener. + */ + oneway void registerRecentTasksListener(in IRecentTasksListener listener) = 1; + + /** + * Unregisters a recent tasks listener. + */ + oneway void unregisterRecentTasksListener(in IRecentTasksListener listener) = 2; + + /** + * Gets the set of recent tasks. + */ + GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl new file mode 100644 index 000000000000..8efa42830d80 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl @@ -0,0 +1,28 @@ +/* + * 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 distshellributed 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.recents; + +/** + * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. + */ +oneway interface IRecentTasksListener { + + /** + * Called when the set of recent tasks change. + */ + void onRecentTasksChanged(); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java new file mode 100644 index 000000000000..a5748f69388f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 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.recents; + +import com.android.wm.shell.common.annotations.ExternalThread; + +/** + * Interface for interacting with the recent tasks. + */ +@ExternalThread +public interface RecentTasks { + /** + * Returns a binder that can be passed to an external process to fetch recent tasks. + */ + default IRecentTasks createExternalInterface() { + return null; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java new file mode 100644 index 000000000000..836a6f610bbd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -0,0 +1,297 @@ +/* + * 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.recents; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.TaskInfo; +import android.content.Context; +import android.os.RemoteException; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import androidx.annotation.BinderThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.wm.shell.common.RemoteCallable; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SingleInstanceRemoteListener; +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.common.annotations.ShellMainThread; +import com.android.wm.shell.util.GroupedRecentTaskInfo; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Manages the recent task list from the system, caching it as necessary. + */ +public class RecentTasksController implements TaskStackListenerCallback, + RemoteCallable<RecentTasksController> { + private static final String TAG = RecentTasksController.class.getSimpleName(); + + private final Context mContext; + private final ShellExecutor mMainExecutor; + private final TaskStackListenerImpl mTaskStackListener; + private final RecentTasks mImpl = new RecentTasksImpl(); + + private final ArrayList<Runnable> mCallbacks = new ArrayList<>(); + // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a + // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1) + private final SparseIntArray mSplitTasks = new SparseIntArray(); + + /** + * Creates {@link RecentTasksController}, returns {@code null} if the feature is not + * supported. + */ + @Nullable + public static RecentTasksController create( + Context context, + TaskStackListenerImpl taskStackListener, + @ShellMainThread ShellExecutor mainExecutor + ) { + if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) { + return null; + } + return new RecentTasksController(context, taskStackListener, mainExecutor); + } + + RecentTasksController(Context context, TaskStackListenerImpl taskStackListener, + ShellExecutor mainExecutor) { + mContext = context; + mTaskStackListener = taskStackListener; + mMainExecutor = mainExecutor; + } + + public RecentTasks asRecentTasks() { + return mImpl; + } + + public void init() { + mTaskStackListener.addListener(this); + } + + /** + * Adds a split pair. This call does not validate the taskIds, only that they are not the same. + */ + public void addSplitPair(int taskId1, int taskId2) { + if (taskId1 == taskId2) { + return; + } + // Remove any previous pairs + removeSplitPair(taskId1); + removeSplitPair(taskId2); + mSplitTasks.put(taskId1, taskId2); + mSplitTasks.put(taskId2, taskId1); + } + + /** + * Removes a split pair. + */ + public void removeSplitPair(int taskId) { + int pairedTaskId = mSplitTasks.get(taskId, INVALID_TASK_ID); + if (pairedTaskId != INVALID_TASK_ID) { + mSplitTasks.delete(taskId); + mSplitTasks.delete(pairedTaskId); + } + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + + @Override + public void onTaskStackChanged() { + notifyRecentTasksChanged(); + } + + @Override + public void onRecentTaskListUpdated() { + // In some cases immediately after booting, the tasks in the system recent task list may be + // loaded, but not in the active task hierarchy in the system. These tasks are displayed in + // overview, but removing them don't result in a onTaskStackChanged() nor a onTaskRemoved() + // callback (those are for changes to the active tasks), but the task list is still updated, + // so we should also invalidate the change id to ensure we load a new list instead of + // reusing a stale list. + notifyRecentTasksChanged(); + } + + public void onTaskRemoved(TaskInfo taskInfo) { + // Remove any split pairs associated with this task + removeSplitPair(taskInfo.taskId); + notifyRecentTasksChanged(); + } + + public void onTaskWindowingModeChanged(TaskInfo taskInfo) { + notifyRecentTasksChanged(); + } + + @VisibleForTesting + void notifyRecentTasksChanged() { + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).run(); + } + } + + private void registerRecentTasksListener(Runnable listener) { + if (!mCallbacks.contains(listener)) { + mCallbacks.add(listener); + } + } + + private void unregisterRecentTasksListener(Runnable listener) { + mCallbacks.remove(listener); + } + + @VisibleForTesting + List<ActivityManager.RecentTaskInfo> getRawRecentTasks(int maxNum, int flags, int userId) { + return ActivityTaskManager.getInstance().getRecentTasks(maxNum, flags, userId); + } + + @VisibleForTesting + ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { + // Note: the returned task list is from the most-recent to least-recent order + final List<ActivityManager.RecentTaskInfo> rawList = getRawRecentTasks(maxNum, flags, + userId); + + // Make a mapping of task id -> task info + final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>(); + for (int i = 0; i < rawList.size(); i++) { + final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + rawMapping.put(taskInfo.taskId, taskInfo); + } + + // Pull out the pairs as we iterate back in the list + ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>(); + for (int i = 0; i < rawList.size(); i++) { + final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + if (!rawMapping.contains(taskInfo.taskId)) { + // If it's not in the mapping, then it was already paired with another task + continue; + } + + final int pairedTaskId = mSplitTasks.get(taskInfo.taskId); + if (pairedTaskId != INVALID_TASK_ID) { + final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); + rawMapping.remove(pairedTaskId); + recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo)); + } else { + recentTasks.add(new GroupedRecentTaskInfo(taskInfo)); + } + } + return recentTasks; + } + + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, + ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); + for (int i = 0; i < recentTasks.size(); i++) { + pw.println(innerPrefix + recentTasks.get(i)); + } + } + + /** + * The interface for calls from outside the Shell, within the host process. + */ + @ExternalThread + private class RecentTasksImpl implements RecentTasks { + private IRecentTasksImpl mIRecentTasks; + + @Override + public IRecentTasks createExternalInterface() { + if (mIRecentTasks != null) { + mIRecentTasks.invalidate(); + } + mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this); + return mIRecentTasks; + } + } + + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IRecentTasksImpl extends IRecentTasks.Stub { + private RecentTasksController mController; + private final SingleInstanceRemoteListener<RecentTasksController, + IRecentTasksListener> mListener; + private final Runnable mRecentTasksListener = + new Runnable() { + @Override + public void run() { + mListener.call(l -> l.onRecentTasksChanged()); + } + }; + + public IRecentTasksImpl(RecentTasksController controller) { + mController = controller; + mListener = new SingleInstanceRemoteListener<>(controller, + c -> c.registerRecentTasksListener(mRecentTasksListener), + c -> c.unregisterRecentTasksListener(mRecentTasksListener)); + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; + } + + @Override + public void registerRecentTasksListener(IRecentTasksListener listener) + throws RemoteException { + executeRemoteCallWithTaskPermission(mController, "registerRecentTasksListener", + (controller) -> mListener.register(listener)); + } + + @Override + public void unregisterRecentTasksListener(IRecentTasksListener listener) + throws RemoteException { + executeRemoteCallWithTaskPermission(mController, "unregisterRecentTasksListener", + (controller) -> mListener.unregister()); + } + + @Override + public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) + throws RemoteException { + final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null}; + executeRemoteCallWithTaskPermission(mController, "getRecentTasks", + (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) + .toArray(new GroupedRecentTaskInfo[0]), + true /* blocking */); + return out[0]; + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java index 78af9df30e6a..ff6f913207f6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java @@ -17,13 +17,10 @@ package com.android.wm.shell.sizecompatui; import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; import android.view.View; -import android.widget.Button; import android.widget.FrameLayout; +import android.widget.LinearLayout; import androidx.annotation.Nullable; @@ -58,10 +55,8 @@ public class SizeCompatHintPopup extends FrameLayout implements View.OnClickList @Override protected void onFinishInflate() { super.onFinishInflate(); - final Button gotItButton = findViewById(R.id.got_it); - gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY), - null /* content */, null /* mask */)); - gotItButton.setOnClickListener(this); + final LinearLayout hintPopup = findViewById(R.id.size_compat_hint_popup); + hintPopup.setOnClickListener(this); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java index 08a840297df1..d75fe5173c5f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java @@ -17,10 +17,6 @@ package com.android.wm.shell.sizecompatui; import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; @@ -63,11 +59,6 @@ public class SizeCompatRestartButton extends FrameLayout implements View.OnClick protected void onFinishInflate() { super.onFinishInflate(); final ImageButton restartButton = findViewById(R.id.size_compat_restart_button); - final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY); - final GradientDrawable mask = new GradientDrawable(); - mask.setShape(GradientDrawable.OVAL); - mask.setColor(color); - restartButton.setBackground(new RippleDrawable(color, null /* content */, mask)); restartButton.setOnClickListener(this); restartButton.setOnLongClickListener(this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java index 7cf95593dbaa..bebb6d30c47f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java @@ -25,6 +25,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; @@ -54,6 +55,10 @@ class SizeCompatUILayout { private final int mTaskId; private ShellTaskOrganizer.TaskListener mTaskListener; private DisplayLayout mDisplayLayout; + private final int mButtonWidth; + private final int mButtonHeight; + private final int mPopupOffsetX; + private final int mPopupOffsetY; @VisibleForTesting final SizeCompatUIWindowManager mButtonWindowManager; @@ -66,9 +71,7 @@ class SizeCompatUILayout { @VisibleForTesting @Nullable SizeCompatHintPopup mHint; - final int mButtonSize; - final int mPopupOffsetX; - final int mPopupOffsetY; + @VisibleForTesting boolean mShouldShowHint; SizeCompatUILayout(SyncTransactionQueue syncQueue, @@ -86,10 +89,13 @@ class SizeCompatUILayout { mShouldShowHint = !hasShownHint; mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this); - mButtonSize = - mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size); - mPopupOffsetX = mButtonSize / 4; - mPopupOffsetY = mButtonSize; + final Resources resources = mContext.getResources(); + mButtonWidth = resources.getDimensionPixelSize(R.dimen.size_compat_button_width); + mButtonHeight = resources.getDimensionPixelSize(R.dimen.size_compat_button_height); + mPopupOffsetX = (mButtonWidth / 2) - resources.getDimensionPixelSize( + R.dimen.size_compat_hint_corner_radius) - (resources.getDimensionPixelSize( + R.dimen.size_compat_hint_point_width) / 2); + mPopupOffsetY = mButtonHeight; } /** Creates the activity restart button window. */ @@ -222,7 +228,7 @@ class SizeCompatUILayout { WindowManager.LayoutParams getButtonWindowLayoutParams() { final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( // Cannot be wrap_content as this determines the actual window size - mButtonSize, mButtonSize, + mButtonWidth, mButtonHeight, TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, PixelFormat.TRANSLUCENT); @@ -278,8 +284,8 @@ class SizeCompatUILayout { // Position of the button in the container coordinate. final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? stableBounds.left - taskBounds.left - : stableBounds.right - taskBounds.left - mButtonSize; - final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize; + : stableBounds.right - taskBounds.left - mButtonWidth; + final int positionY = stableBounds.bottom - taskBounds.top - mButtonHeight; updateSurfacePosition(leash, positionX, positionY); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index 6440ef0b6b11..082fe9205be8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -16,8 +16,6 @@ package com.android.wm.shell.splitscreen; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; - import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; @@ -56,7 +54,6 @@ class MainStage extends StageTaskListener { final WindowContainerToken rootToken = mRootTaskInfo.token; wct.setBounds(rootToken, rootBounds) - .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW) // Moving the root task to top after the child tasks were re-parented , or the root // task cannot be visible and focused. .reorder(rootToken, true /* onTop */); @@ -83,11 +80,7 @@ class MainStage extends StageTaskListener { if (mRootTaskInfo == null) return; final WindowContainerToken rootToken = mRootTaskInfo.token; - wct.setLaunchRoot( - rootToken, - null, - null) - .reparentTasks( + wct.reparentTasks( rootToken, null /* newParent */, CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, @@ -97,9 +90,4 @@ class MainStage extends StageTaskListener { // all its tasks. .reorder(rootToken, false /* onTop */); } - - void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) { - wct.setBounds(mRootTaskInfo.token, bounds) - .setWindowingMode(mRootTaskInfo.token, windowingMode); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 51104e44569e..f8c03044c5c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -45,14 +45,13 @@ class SideStage extends StageTaskListener { stageTaskUnfoldController); } - void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds, - WindowContainerTransaction wct) { + void moveToTop(Rect rootBounds, WindowContainerTransaction wct) { final WindowContainerToken rootToken = mRootTaskInfo.token; - wct.setBounds(rootToken, rootBounds) - .reparent(task.token, rootToken, true /* onTop*/) - // Moving the root task to top after the child tasks were reparented , or the root - // task cannot be visible and focused. - .reorder(rootToken, true /* onTop */); + wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */); + } + + void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) { + wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/); } boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { 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 36f140614deb..14a6574e51a7 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 @@ -34,7 +34,6 @@ import android.content.Intent; import android.content.pm.LauncherApps; import android.graphics.Rect; import android.os.Bundle; -import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; @@ -61,6 +60,7 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; @@ -433,46 +433,26 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @BinderThread private static class ISplitScreenImpl extends ISplitScreen.Stub { private SplitScreenController mController; - private ISplitScreenListener mListener; + private final SingleInstanceRemoteListener<SplitScreenController, + ISplitScreenListener> mListener; private final SplitScreen.SplitScreenListener mSplitScreenListener = new SplitScreen.SplitScreenListener() { @Override public void onStagePositionChanged(int stage, int position) { - try { - if (mListener != null) { - mListener.onStagePositionChanged(stage, position); - } - } catch (RemoteException e) { - Slog.e(TAG, "onStagePositionChanged", e); - } + mListener.call(l -> l.onStagePositionChanged(stage, position)); } @Override public void onTaskStageChanged(int taskId, int stage, boolean visible) { - try { - if (mListener != null) { - mListener.onTaskStageChanged(taskId, stage, visible); - } - } catch (RemoteException e) { - Slog.e(TAG, "onTaskStageChanged", e); - } - } - }; - private final IBinder.DeathRecipient mListenerDeathRecipient = - new IBinder.DeathRecipient() { - @Override - @BinderThread - public void binderDied() { - final SplitScreenController controller = mController; - controller.getRemoteCallExecutor().execute(() -> { - mListener = null; - controller.unregisterSplitScreenListener(mSplitScreenListener); - }); + mListener.call(l -> l.onTaskStageChanged(taskId, stage, visible)); } }; public ISplitScreenImpl(SplitScreenController controller) { mController = controller; + mListener = new SingleInstanceRemoteListener<>(controller, + c -> c.registerSplitScreenListener(mSplitScreenListener), + c -> c.unregisterSplitScreenListener(mSplitScreenListener)); } /** @@ -485,36 +465,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void registerSplitScreenListener(ISplitScreenListener listener) { executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener", - (controller) -> { - if (mListener != null) { - mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } - if (listener != null) { - try { - listener.asBinder().linkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to link to death"); - return; - } - } - mListener = listener; - controller.registerSplitScreenListener(mSplitScreenListener); - }); + (controller) -> mListener.register(listener)); } @Override public void unregisterSplitScreenListener(ISplitScreenListener listener) { executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener", - (controller) -> { - if (mListener != null) { - mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } - mListener = null; - controller.unregisterSplitScreenListener(mSplitScreenListener); - }); + (controller) -> mListener.unregister()); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 3b62afc7320e..4fa1eadb0bc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -268,11 +268,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean moveToSideStage(ActivityManager.RunningTaskInfo task, @SplitPosition int sideStagePosition) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); setSideStagePosition(sideStagePosition, wct); - mMainStage.activate(getMainStageBounds(), wct, true /* reparent */); - mSideStage.addTask(task, getSideStageBounds(), wct); - mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t)); + mSideStage.evictAllChildren(evictWct); + mSideStage.addTask(task, wct); + if (!evictWct.isEmpty()) { + wct.merge(evictWct, true /* transfer */); + } + mTaskOrganizer.applyTransaction(wct); return true; } @@ -337,8 +340,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget(); try { - ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( - adapter.getCallingApplication()); + try { + ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( + adapter.getCallingApplication()); + } catch (SecurityException e) { + Slog.e(TAG, "Unable to boost animation thread. This should only happen" + + " during unit tests"); + } adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); } catch (RemoteException e) { @@ -758,8 +766,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); // Make sure the main stage is active. mMainStage.activate(getMainStageBounds(), wct, true /* reparent */); - mSideStage.setBounds(getSideStageBounds(), wct); - mTaskOrganizer.applyTransaction(wct); + mSideStage.moveToTop(getSideStageBounds(), wct); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t)); } if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java index 4e477ca104dd..003d8a3f2fef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java @@ -18,6 +18,8 @@ package com.android.wm.shell.startingsurface; import static android.view.Choreographer.CALLBACK_COMMIT; import static android.view.View.GONE; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM; + import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; @@ -42,6 +44,7 @@ import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.window.SplashScreenView; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.TransactionPool; @@ -311,17 +314,19 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { @Override public void onAnimationStart(Animator animation) { - // ignore + InteractionJankMonitor.getInstance().begin(mSplashScreenView, CUJ_SPLASHSCREEN_EXIT_ANIM); } @Override public void onAnimationEnd(Animator animation) { reset(); + InteractionJankMonitor.getInstance().end(CUJ_SPLASHSCREEN_EXIT_ANIM); } @Override public void onAnimationCancel(Animator animation) { reset(); + InteractionJankMonitor.getInstance().cancel(CUJ_SPLASHSCREEN_EXIT_ANIM); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index e2a72bdc872a..709e2219a64e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -19,6 +19,7 @@ package com.android.wm.shell.startingsurface; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.annotation.NonNull; @@ -263,11 +264,12 @@ public class SplashscreenIconDrawableFactory { * A lightweight AdaptiveIconDrawable which support foreground to be Animatable, and keep this * drawable masked by config_icon_mask. */ - private static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable + public static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable implements SplashScreenView.IconAnimateListener { private Animatable mAnimatableIcon; private Animator mIconAnimator; private boolean mAnimationTriggered; + private AnimatorListenerAdapter mJankMonitoringListener; AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) { super(foregroundDrawable); @@ -275,6 +277,11 @@ public class SplashscreenIconDrawableFactory { } @Override + public void setAnimationJankMonitoring(AnimatorListenerAdapter listener) { + mJankMonitoringListener = listener; + } + + @Override public boolean prepareAnimate(long duration, Runnable startListener) { mAnimatableIcon = (Animatable) mForegroundDrawable; mIconAnimator = ValueAnimator.ofInt(0, 1); @@ -286,6 +293,9 @@ public class SplashscreenIconDrawableFactory { startListener.run(); } try { + if (mJankMonitoringListener != null) { + mJankMonitoringListener.onAnimationStart(animation); + } mAnimatableIcon.start(); } catch (Exception ex) { Log.e(TAG, "Error while running the splash screen animated icon", ex); @@ -296,11 +306,17 @@ public class SplashscreenIconDrawableFactory { @Override public void onAnimationEnd(Animator animation) { mAnimatableIcon.stop(); + if (mJankMonitoringListener != null) { + mJankMonitoringListener.onAnimationEnd(animation); + } } @Override public void onAnimationCancel(Animator animation) { mAnimatableIcon.stop(); + if (mJankMonitoringListener != null) { + mJankMonitoringListener.onAnimationCancel(animation); + } } @Override @@ -316,6 +332,7 @@ public class SplashscreenIconDrawableFactory { public void stopAnimation() { if (mIconAnimator != null && mIconAnimator.isRunning()) { mIconAnimator.end(); + mJankMonitoringListener = null; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index a86e07a5602d..e98a3e87c0b7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -46,6 +46,7 @@ import com.android.internal.util.function.TriConsumer; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TransactionPool; /** @@ -237,24 +238,19 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo @BinderThread private static class IStartingWindowImpl extends IStartingWindow.Stub { private StartingWindowController mController; - private IStartingWindowListener mListener; + private SingleInstanceRemoteListener<StartingWindowController, + IStartingWindowListener> mListener; private final TriConsumer<Integer, Integer, Integer> mStartingWindowListener = - this::notifyIStartingWindowListener; - private final IBinder.DeathRecipient mListenerDeathRecipient = - new IBinder.DeathRecipient() { - @Override - @BinderThread - public void binderDied() { - final StartingWindowController controller = mController; - controller.getRemoteCallExecutor().execute(() -> { - mListener = null; - controller.setStartingWindowListener(null); - }); - } + (taskId, supportedType, startingWindowBackgroundColor) -> { + mListener.call(l -> l.onTaskLaunching(taskId, supportedType, + startingWindowBackgroundColor)); }; public IStartingWindowImpl(StartingWindowController controller) { mController = controller; + mListener = new SingleInstanceRemoteListener<>(controller, + c -> c.setStartingWindowListener(mStartingWindowListener), + c -> c.setStartingWindowListener(null)); } /** @@ -268,36 +264,12 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo public void setStartingWindowListener(IStartingWindowListener listener) { executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener", (controller) -> { - if (mListener != null) { - // Reset the old death recipient - mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } if (listener != null) { - try { - listener.asBinder().linkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to link to death"); - return; - } + mListener.register(listener); + } else { + mListener.unregister(); } - mListener = listener; - controller.setStartingWindowListener(mStartingWindowListener); }); } - - private void notifyIStartingWindowListener(int taskId, int supportedType, - int startingWindowBackgroundColor) { - if (mListener == null) { - return; - } - - try { - mListener.onTaskLaunching(taskId, supportedType, startingWindowBackgroundColor); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to notify task launching", e); - } - } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index 3be896e4aca3..3e2a0e635a75 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -18,9 +18,11 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityTaskManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import android.util.Slog; import android.view.SurfaceControl; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; @@ -89,6 +91,13 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { if (mRemote.asBinder() != null) { mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */); } + try { + ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( + mRemote.getAppThread()); + } catch (SecurityException e) { + Slog.e(Transitions.TAG, "Unable to boost animation thread. This should only happen" + + " during unit tests"); + } mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error running remote transition.", e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index c798ace18b5f..ece9f47e8788 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -18,6 +18,7 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityTaskManager; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; @@ -130,6 +131,13 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { }; try { handleDeath(remote.asBinder(), finishCallback); + try { + ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( + remote.getAppThread()); + } catch (SecurityException e) { + Log.e(Transitions.TAG, "Unable to boost animation thread. This should only happen" + + " during unit tests"); + } remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error running remote transition.", e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java index 802d25f66340..b34049d4ec42 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java @@ -38,11 +38,11 @@ public interface ShellTransitions { /** * Registers a remote transition. */ - void registerRemote(@NonNull TransitionFilter filter, - @NonNull RemoteTransition remoteTransition); + default void registerRemote(@NonNull TransitionFilter filter, + @NonNull RemoteTransition remoteTransition) {} /** * Unregisters a remote transition. */ - void unregisterRemote(@NonNull RemoteTransition remoteTransition); + default void unregisterRemote(@NonNull RemoteTransition remoteTransition) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index c36983189a71..804e449decf8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -171,24 +171,6 @@ public class Transitions implements RemoteCallable<Transitions> { } } - /** Create an empty/non-registering transitions object for system-ui tests. */ - @VisibleForTesting - public static ShellTransitions createEmptyForTesting() { - return new ShellTransitions() { - @Override - public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter, - @androidx.annotation.NonNull RemoteTransition remoteTransition) { - // Do nothing - } - - @Override - public void unregisterRemote( - @androidx.annotation.NonNull RemoteTransition remoteTransition) { - // Do nothing - } - }; - } - /** Register this transition handler with Core */ public void register(ShellTaskOrganizer taskOrganizer) { if (mPlayerImpl == null) return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl new file mode 100644 index 000000000000..15797cdb9aba --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.util; + +parcelable GroupedRecentTaskInfo;
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java new file mode 100644 index 000000000000..0331ba19defe --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java @@ -0,0 +1,86 @@ +/* + * 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.util; + +import android.app.ActivityManager; +import android.app.WindowConfiguration; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Simple container for recent tasks. May contain either a single or pair of tasks. + */ +public class GroupedRecentTaskInfo implements Parcelable { + public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1; + public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2; + + public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) { + this(task1, null); + } + + public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1, + @Nullable ActivityManager.RecentTaskInfo task2) { + mTaskInfo1 = task1; + mTaskInfo2 = task2; + } + + GroupedRecentTaskInfo(Parcel parcel) { + mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); + mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); + } + + @Override + public String toString() { + return "Task1: " + getTaskInfo(mTaskInfo1) + ", Task2: " + getTaskInfo(mTaskInfo2); + } + + private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { + if (taskInfo == null) { + return null; + } + return "id=" + taskInfo.taskId + + " baseIntent=" + (taskInfo.baseIntent != null + ? taskInfo.baseIntent.getComponent() + : "null") + + " winMode=" + WindowConfiguration.windowingModeToString( + taskInfo.getWindowingMode()); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeTypedObject(mTaskInfo1, flags); + parcel.writeTypedObject(mTaskInfo2, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR = + new Creator<GroupedRecentTaskInfo>() { + public GroupedRecentTaskInfo createFromParcel(Parcel source) { + return new GroupedRecentTaskInfo(source); + } + public GroupedRecentTaskInfo[] newArray(int size) { + return new GroupedRecentTaskInfo[size]; + } + }; +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index d5acbbcf7d2c..1fcbf14fb732 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -65,6 +65,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Optional; /** * Tests for the shell task organizer. @@ -131,7 +132,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mSizeCompatUI)); + mSizeCompatUI, Optional.empty())); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java index 5bdf831a81f4..6080f3ae78e8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java @@ -26,6 +26,7 @@ import androidx.test.InstrumentationRegistry; import org.junit.After; import org.junit.Before; +import org.mockito.MockitoAnnotations; /** * Base class that does shell test case setup. @@ -36,6 +37,7 @@ public abstract class ShellTestCase { @Before public void shellSetup() { + MockitoAnnotations.initMocks(this); final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final DisplayManager dm = context.getSystemService(DisplayManager.class); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java new file mode 100644 index 000000000000..a1e12319ac70 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -0,0 +1,213 @@ +/* + * 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.recents; + +import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import static java.lang.Integer.MAX_VALUE; + +import android.app.ActivityManager; +import android.app.WindowConfiguration; +import android.content.Context; +import android.view.SurfaceControl; +import android.window.TaskAppearedInfo; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.util.GroupedRecentTaskInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; + +/** + * Tests for {@link RecentTasksController}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RecentTasksControllerTest extends ShellTestCase { + + @Mock + private Context mContext; + @Mock + private TaskStackListenerImpl mTaskStackListener; + + private ShellTaskOrganizer mShellTaskOrganizer; + private RecentTasksController mRecentTasksController; + private ShellExecutor mMainExecutor; + + @Before + public void setUp() { + mMainExecutor = new TestShellExecutor(); + mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener, + mMainExecutor)); + mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext, + null /* sizeCompatUI */, Optional.of(mRecentTasksController)); + } + + @Test + public void testGetRecentTasks() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + setRawList(t1, t2, t3); + + ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( + MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + assertGroupedTasksListEquals(recentTasks, + t1.taskId, -1, + t2.taskId, -1, + t3.taskId, -1); + } + + @Test + public void testGetRecentTasks_withPairs() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + setRawList(t1, t2, t3, t4, t5, t6); + + // Mark a couple pairs [t2, t4], [t3, t5] + mRecentTasksController.addSplitPair(t2.taskId, t4.taskId); + mRecentTasksController.addSplitPair(t3.taskId, t5.taskId); + + ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( + MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + assertGroupedTasksListEquals(recentTasks, + t1.taskId, -1, + t2.taskId, t4.taskId, + t3.taskId, t5.taskId, + t6.taskId, -1); + } + + @Test + public void testRemovedTaskRemovesSplit() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + setRawList(t1, t2, t3); + + // Add a pair + mRecentTasksController.addSplitPair(t2.taskId, t3.taskId); + reset(mRecentTasksController); + + // Remove one of the tasks and ensure the pair is removed + SurfaceControl mockLeash = mock(SurfaceControl.class); + ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2); + mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash); + mShellTaskOrganizer.onTaskVanished(rt2); + + verify(mRecentTasksController).removeSplitPair(t2.taskId); + } + + @Test + public void testTaskWindowingModeChangedNotifiesChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + setRawList(t1); + + // Remove one of the tasks and ensure the pair is removed + SurfaceControl mockLeash = mock(SurfaceControl.class); + ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2); + rt2Fullscreen.configuration.windowConfiguration.setWindowingMode( + WINDOWING_MODE_FULLSCREEN); + mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash); + + // Change the windowing mode and ensure the recent tasks change is notified + ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2); + rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW); + mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow); + + verify(mRecentTasksController).notifyRecentTasksChanged(); + } + + /** + * Helper to create a task with a given task id. + */ + private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) { + ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo(); + info.taskId = taskId; + return info; + } + + /** + * Helper to create a running task with a given task id. + */ + private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) { + ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); + info.taskId = taskId; + return info; + } + + /** + * Helper to set the raw task list on the controller. + */ + private ArrayList<ActivityManager.RecentTaskInfo> setRawList( + ActivityManager.RecentTaskInfo... tasks) { + ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>(); + for (ActivityManager.RecentTaskInfo task : tasks) { + rawList.add(task); + } + doReturn(rawList).when(mRecentTasksController).getRawRecentTasks(anyInt(), anyInt(), + anyInt()); + return rawList; + } + + /** + * Asserts that the recent tasks matches the given task ids. + * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in + * the grouped task list + */ + private void assertGroupedTasksListEquals(ArrayList<GroupedRecentTaskInfo> recentTasks, + int... expectedTaskIds) { + int[] flattenedTaskIds = new int[recentTasks.size() * 2]; + for (int i = 0; i < recentTasks.size(); i++) { + GroupedRecentTaskInfo pair = recentTasks.get(i); + flattenedTaskIds[2 * i] = pair.mTaskInfo1.taskId; + flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null + ? pair.mTaskInfo2.taskId + : -1; + } + assertTrue("Expected: " + Arrays.toString(expectedTaskIds) + + " Received: " + Arrays.toString(flattenedTaskIds), + Arrays.equals(flattenedTaskIds, expectedTaskIds)); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java index 10fd7d705967..3a14a336190d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java @@ -24,7 +24,7 @@ import static org.mockito.Mockito.verify; import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; -import android.widget.Button; +import android.widget.LinearLayout; import androidx.test.filters.SmallTest; @@ -77,8 +77,8 @@ public class SizeCompatHintPopupTest extends ShellTestCase { public void testOnClick() { doNothing().when(mLayout).dismissHint(); - final Button button = mHint.findViewById(R.id.got_it); - button.performClick(); + final LinearLayout hintPopup = mHint.findViewById(R.id.size_compat_hint_popup); + hintPopup.performClick(); verify(mLayout).dismissHint(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java index 1857faadb433..a31aa58bdc26 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java @@ -74,7 +74,7 @@ public class SideStageTests extends ShellTestCase { public void testAddTask() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); - mSideStage.addTask(task, mRootTask.configuration.windowConfiguration.getBounds(), mWct); + mSideStage.addTask(task, mWct); verify(mWct).reparent(eq(task.token), eq(mRootTask.token), eq(true)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index cd29220bb96a..617e94a4e0a4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -113,10 +113,7 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.moveToSideStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT); - verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class), - eq(true /* includingTopTask */)); - verify(mSideStage).addTask(eq(task), any(Rect.class), - any(WindowContainerTransaction.class)); + verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class)); } @Test diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index b4cafd8548f4..edbfd2a8f03e 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -254,8 +254,14 @@ public class CompanionDeviceActivity extends Activity { Log.i(LOG_TAG, "onDeviceConfirmed(selectedDevice = " + selectedDevice + ")"); getService().onDeviceSelected( getCallingPackage(), getDeviceMacAddress(selectedDevice.device)); + } + + void setResultAndFinish() { + Log.i(LOG_TAG, "setResultAndFinish(selectedDevice = " + + getService().mSelectedDevice.device + ")"); setResult(RESULT_OK, - new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device)); + new Intent().putExtra( + CompanionDeviceManager.EXTRA_DEVICE, getService().mSelectedDevice.device)); finish(); } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index c24782e8b310..5df8e3c83a7a 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -117,6 +117,11 @@ public class CompanionDeviceDiscoveryService extends Service { CompanionDeviceDiscoveryService::startDiscovery, CompanionDeviceDiscoveryService.this, request)); } + + @Override + public void onAssociationCreated() { + Handler.getMain().post(CompanionDeviceDiscoveryService.this::onAssociationCreated); + } }; private ScanCallback mBLEScanCallback; @@ -222,6 +227,11 @@ public class CompanionDeviceDiscoveryService extends Service { SCAN_TIMEOUT); } + @MainThread + private void onAssociationCreated() { + mActivity.setResultAndFinish(); + } + private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) { return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters); } diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml index c050d39c236e..522410167eef 100644 --- a/packages/PackageInstaller/res/values-as/strings.xml +++ b/packages/PackageInstaller/res/values-as/strings.xml @@ -56,7 +56,7 @@ <string name="uninstall_application_text" msgid="3816830743706143980">"আপুনি এই এপটো আনইনষ্টল কৰিব বিচাৰেনে?"</string> <string name="uninstall_application_text_all_users" msgid="575491774380227119">"আপুনি "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ বাবে এই এপটো আনইনষ্টল কৰিব বিচাৰেনে? এপ্লিকেশ্বন আৰু ইয়াৰ ডেটা ডিভাইচটোত থকা "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ পৰা আঁতৰোৱা হ\'ব৷"</string> <string name="uninstall_application_text_user" msgid="498072714173920526">"আপুনি ব্যৱহাৰকাৰীৰ <xliff:g id="USERNAME">%1$s</xliff:g> বাবে এই এপটো আনইনষ্টল কৰিব বিচাৰেনে?"</string> - <string name="uninstall_update_text" msgid="863648314632448705">"এই এপটোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? সকলো ডেটা মচা হ\'ব।"</string> + <string name="uninstall_update_text" msgid="863648314632448705">"এই এপ্টোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? আটাইবোৰ ডেটা মচা হ\'ব।"</string> <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"এই এপটোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? সকলো ডেটা মচা হ\'ব। কর্মস্থানৰ প্ৰফাইল থকা ব্যৱহাৰকাৰীৰ লগতে ডিভাইচটোৰ সকলো ব্যৱহাৰকাৰীৰ ওপৰত ইয়াৰ প্ৰভাৱ পৰিব।"</string> <string name="uninstall_keep_data" msgid="7002379587465487550">"এপৰ ডেটাৰ <xliff:g id="SIZE">%1$s</xliff:g> ৰাখক"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"আনইনষ্টল কৰি থকা হৈছে"</string> diff --git a/packages/PrintSpooler/res/values-as/strings.xml b/packages/PrintSpooler/res/values-as/strings.xml index a93fceb87959..020eac770134 100644 --- a/packages/PrintSpooler/res/values-as/strings.xml +++ b/packages/PrintSpooler/res/values-as/strings.xml @@ -28,7 +28,7 @@ <string name="label_orientation" msgid="2853142581990496477">"দিশ"</string> <string name="label_pages" msgid="7768589729282182230">"পৃষ্ঠাসমূহ"</string> <string name="destination_default_text" msgid="5422708056807065710">"প্ৰিণ্টাৰ বাছনি কৰক"</string> - <string name="template_all_pages" msgid="3322235982020148762">"সকলো <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string> + <string name="template_all_pages" msgid="3322235982020148762">"আটাইবোৰ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string> <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>ৰ পৰিসৰ"</string> <string name="pages_range_example" msgid="8558694453556945172">"যেনে: ১—৫, ৮, ১১—১৩"</string> <string name="print_preview" msgid="8010217796057763343">"প্ৰিণ্টৰ পূৰ্বদৰ্শন"</string> @@ -36,7 +36,7 @@ <string name="printing_app_crashed" msgid="854477616686566398">"প্ৰিণ্টিং এপ্ ক্ৰেশ্ব হৈছে"</string> <string name="generating_print_job" msgid="3119608742651698916">"প্ৰিণ্টিং প্ৰস্তুত কৰি আছে"</string> <string name="save_as_pdf" msgid="5718454119847596853">"PDF ৰূপে ছেভ কৰক"</string> - <string name="all_printers" msgid="5018829726861876202">"সকলো প্ৰিণ্টাৰ…"</string> + <string name="all_printers" msgid="5018829726861876202">"আটাইবোৰ প্ৰিণ্টাৰ…"</string> <string name="print_dialog" msgid="32628687461331979">"প্ৰিণ্ট সংবাদ"</string> <string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g> /<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string> <string name="page_description_template" msgid="6831239682256197161">"পৃষ্ঠা <xliff:g id="PAGE_COUNT">%2$d</xliff:g>ৰ <xliff:g id="CURRENT_PAGE">%1$d</xliff:g>"</string> @@ -48,7 +48,7 @@ <string name="print_options_expanded" msgid="6944679157471691859">"প্ৰিণ্ট বিকল্পসমূহ বিস্তাৰ কৰা হ’ল"</string> <string name="print_options_collapsed" msgid="7455930445670414332">"প্ৰিণ্ট বিকল্পসমূহ সংকুচিত কৰা হ’ল"</string> <string name="search" msgid="5421724265322228497">"সন্ধান কৰক"</string> - <string name="all_printers_label" msgid="3178848870161526399">"সকলো প্ৰিণ্টাৰ"</string> + <string name="all_printers_label" msgid="3178848870161526399">"আটাইবোৰ প্ৰিণ্টাৰ"</string> <string name="add_print_service_label" msgid="5356702546188981940">"সেৱা যোগ কৰক"</string> <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"সন্ধান বাকচটো দেখুওৱা হ’ল"</string> <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"সন্ধান বাকচটো ঢাক খাই আছে"</string> @@ -74,7 +74,7 @@ <string name="enabled_services_title" msgid="7036986099096582296">"সক্ষম কৰা সেৱাসমূহ"</string> <string name="recommended_services_title" msgid="3799434882937956924">"অনুমোদিত সেৱাসমূহ"</string> <string name="disabled_services_title" msgid="7313253167968363211">"অক্ষম কৰা সেৱাসমূহ"</string> - <string name="all_services_title" msgid="5578662754874906455">"সকলো সেৱা"</string> + <string name="all_services_title" msgid="5578662754874906455">"আটাইবোৰ সেৱা"</string> <plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138"> <item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g>টা প্ৰিণ্টাৰ বিচাৰিবলৈ ইনষ্টল কৰক</item> <item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g>টা প্ৰিণ্টাৰ বিচাৰিবলৈ ইনষ্টল কৰক</item> diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp new file mode 100644 index 000000000000..fc82b79399ef --- /dev/null +++ b/packages/SettingsLib/ActivityEmbedding/Android.bp @@ -0,0 +1,21 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_library { + name: "SettingsLibActivityEmbedding", + + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.annotation_annotation", + "SettingsLibUtils", + ], + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml new file mode 100644 index 000000000000..2e6c405f9529 --- /dev/null +++ b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.activityembedding"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java new file mode 100644 index 000000000000..36c2bda8b03a --- /dev/null +++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java @@ -0,0 +1,51 @@ +/* + * 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.settingslib.activityembedding; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; + +import com.android.settingslib.utils.BuildCompatUtils; + +/** + * An util class collecting all common methods for the embedding activity features. + */ +public class ActivityEmbeddingUtils { + private static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = + "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY"; + private static final String PACKAGE_NAME_SETTINGS = "com.android.settings"; + + /** + * Whether to support embedding activity feature. + */ + public static boolean isEmbeddingActivityEnabled(Context context) { + if (BuildCompatUtils.isAtLeastS()) { + final Intent intent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); + intent.setPackage(PACKAGE_NAME_SETTINGS); + final ResolveInfo resolveInfo = + context.getPackageManager().resolveActivity(intent, 0 /* flags */); + return resolveInfo != null + && resolveInfo.activityInfo != null + && resolveInfo.activityInfo.enabled; + } + return false; + } + + private ActivityEmbeddingUtils() { + } +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index a65bf41210a1..7560c41b2e29 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -73,6 +73,7 @@ java_defaults { "SettingsLibCollapsingToolbarBaseActivity", "SettingsLibTwoTargetPreference", "SettingsLibSettingsTransition", + "SettingsLibActivityEmbedding", ], } diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 39aacfdfd658..057349fd25ba 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -284,7 +284,7 @@ <string name="wifi_display_certification_summary" msgid="8111151348106907513">"نمایش گزینهها برای گواهینامه نمایش بیسیم"</string> <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"افزایش سطح گزارشگیری Wi‑Fi، نمایش به ازای SSID RSSI در انتخابکننده Wi‑Fi"</string> <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"تخلیه باتری راکاهش میدهد و عملکرد شبکه را بهبود میبخشد"</string> - <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"اگر این حالت فعال باشد، هر بار این دستگاه به شبکهای متصل شود که تصادفیسازی MAC در آن فعال است، ممکن است نشانی MAC آن تغییر کند."</string> + <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"اگر این حالت فعال باشد، هر بار این دستگاه به شبکهای متصل شود که تصادفیسازی «واپایش دسترسی رسانه» در آن فعال است، ممکن است «نشانی واد» آن تغییر کند."</string> <string name="wifi_metered_label" msgid="8737187690304098638">"محدودشده"</string> <string name="wifi_unmetered_label" msgid="6174142840934095093">"محدودنشده"</string> <string name="select_logd_size_title" msgid="1604578195914595173">"اندازههای حافظه موقت ثبتکننده"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 9f2d8a2d346e..286d99418135 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -524,7 +524,7 @@ <string name="zen_mode_duration_always_prompt_title" msgid="3212996860498119555">"יש לשאול בכל פעם"</string> <string name="zen_mode_forever" msgid="3339224497605461291">"עד הכיבוי"</string> <string name="time_unit_just_now" msgid="3006134267292728099">"הרגע"</string> - <string name="media_transfer_this_device_name" msgid="2716555073132169240">"רמקול של הטלפון"</string> + <string name="media_transfer_this_device_name" msgid="2716555073132169240">"הרמקול של הטלפון"</string> <string name="media_transfer_this_phone" msgid="7194341457812151531">"הטלפון הזה"</string> <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"יש בעיה בחיבור. עליך לכבות את המכשיר ולהפעיל אותו מחדש"</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"התקן אודיו חוטי"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 5ed5f39e0ebd..f982eeea84e8 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -454,7 +454,7 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi hingga penuh"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi hingga penuh"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan terhad buat sementara waktu"</string> + <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan terhad sementara"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengecas dgn cepat"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 2f571069b788..9f4e544a2be4 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -169,7 +169,7 @@ <string name="tts_play_example_summary" msgid="634044730710636383">"အသံပေါင်းစပ်ခြင်းအတွက် တိုတောင်းသောသရုပ်ပြမှုကို ပြခြင်း"</string> <string name="tts_install_data_title" msgid="1829942496472751703">"အသံဒေတာများကို ထည့်သွင်းခြင်း"</string> <string name="tts_install_data_summary" msgid="3608874324992243851">"စကားသံပေါင်းစပ်မှုအတွက်လိုအပ်သောအသံဒေတာအား ထည့်သွင်းမည်"</string> - <string name="tts_engine_security_warning" msgid="3372432853837988146">"ဤစကားသံပေါင်းစပ်အင်ဂျင်အားအသုံးပြုရာရာတွင် သင့်ကိုယ်ရေးအချက်အလက်များဖြစ်သော စကားဝှက်များနှင့် ကရက်ဒစ်ကဒ်နံပါတ်စသည်တို့အပါအဝင် သင်ပြောဆိုသောစာသားများအားလုံးကို ရယူသွားမည်ဖြစ်သည်။ <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>အင်ဂျင်မှ လာပါသည်။ ဤစကားသံပေါင်းစပ်အင်ဂျင်ကို အသုံးပြုမည်လား?"</string> + <string name="tts_engine_security_warning" msgid="3372432853837988146">"ဤစကားသံပေါင်းစပ်စနစ်အားအသုံးပြုရာရာတွင် သင့်ကိုယ်ရေးအချက်အလက်များဖြစ်သော စကားဝှက်များနှင့် ကရက်ဒစ်ကတ်နံပါတ်စသည်တို့အပါအဝင် သင်ပြောဆိုသောစာသားများအားလုံးကို ရယူသွားမည်ဖြစ်သည်။ ဤစနစ်သည် <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> မှ လာပါသည်။ ဤစကားသံပေါင်းစပ်စနစ်ကို အသုံးပြုမလား။"</string> <string name="tts_engine_network_required" msgid="8722087649733906851">"ဤဘာသာစကားသည် စာသားမှ အသံထွက်ရန် အလုပ်လုပ်သော ကွန်ရက်ချိတ်ဆက်မှု လိုအပ်သည်။"</string> <string name="tts_default_sample_string" msgid="6388016028292967973">"ဤသည်မှာ အသံတုလုပ်ခြင်း ၏ နမူနာတစ်ခုဖြစ်သည်။"</string> <string name="tts_status_title" msgid="8190784181389278640">"လက်ရှိဘာသာစကားအခြေအနေ"</string> @@ -489,7 +489,7 @@ <string name="active_input_method_subtypes" msgid="4232680535471633046">"ရနိုင်သောထည့်သွင်းရန်နည်းလမ်းများ"</string> <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"သတ်မှတ် ဘာသာစကားများကို သုံးပါ"</string> <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>အတွက် ဆက်တင်းများဖွင့်ရန် မအောင်မြင်ပါ။"</string> - <string name="ime_security_warning" msgid="6547562217880551450">"ဤထည့်သွင်းမှုနည်းလမ်းမှာ သင့်ကိုယ်ရေးအချက်အလက်များဖြစ်သော စကားဝှက်များနှင့် ကရက်ဒစ်ကဒ်နံပါတ်စသည်တို့ကို ရယူသွားမည်ဖြစ်သည်။ <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>အပလီကေးရှင်းမှလာပါသည်။ ဤထည့်သွင်းမှုနည်းလမ်းကို အသုံးပြုမည်လား?"</string> + <string name="ime_security_warning" msgid="6547562217880551450">"ဤထည့်သွင်းမှုနည်းလမ်းမှာ သင့်ကိုယ်ရေးအချက်အလက်များဖြစ်သော စကားဝှက်များနှင့် ခရက်ဒစ်ကတ်နံပါတ်စသည်တို့ကို ရယူသွားမည်ဖြစ်သည်။ ဤ <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> အပလီကေးရှင်းမှလာပါသည်။ ဤထည့်သွင်းမှုနည်းလမ်းကို အသုံးပြုမလား။"</string> <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"မှတ်ချက် − ပြန်လည်စတင်ပြီးနောက် သင့်ဖုန်းကိုလော့ခ်မဖွင့်မချင်း ဤအက်ပ်ကို အသုံးပြု၍မရပါ"</string> <string name="ims_reg_title" msgid="8197592958123671062">"IMS မှတ်ပုံတင်ခြင်း အခြေအနေ"</string> <string name="ims_reg_status_registered" msgid="884916398194885457">"မှတ်ပုံတင်ထားသည်"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index ff799e09e428..7d2b4d912cef 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -524,7 +524,7 @@ <string name="zen_mode_duration_always_prompt_title" msgid="3212996860498119555">"Всегда спрашивать"</string> <string name="zen_mode_forever" msgid="3339224497605461291">"Пока вы не отключите"</string> <string name="time_unit_just_now" msgid="3006134267292728099">"Только что"</string> - <string name="media_transfer_this_device_name" msgid="2716555073132169240">"Встроенный динамик"</string> + <string name="media_transfer_this_device_name" msgid="2716555073132169240">"Встроен. динамик"</string> <string name="media_transfer_this_phone" msgid="7194341457812151531">"Этот смартфон"</string> <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ошибка подключения. Выключите и снова включите устройство."</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Проводное аудиоустройство"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index a67ea6a06381..cb4cbf8b2ce2 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -552,7 +552,7 @@ <string name="user_setup_dialog_message" msgid="269931619868102841">"Đảm bảo người dùng có mặt để tự thiết lập không gian của mình trên thiết bị"</string> <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Thiết lập tiểu sử ngay bây giờ?"</string> <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Thiết lập ngay"</string> - <string name="user_setup_button_setup_later" msgid="8712980133555493516">"Không phải bây giờ"</string> + <string name="user_setup_button_setup_later" msgid="8712980133555493516">"Để sau"</string> <string name="user_add_user_type_title" msgid="551279664052914497">"Thêm"</string> <string name="user_new_user_name" msgid="60979820612818840">"Người dùng mới"</string> <string name="user_new_profile_name" msgid="2405500423304678841">"Tiểu sử mới"</string> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index c6cca5adaeb0..eae6f27db738 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -50,14 +50,31 @@ java_library { srcs: ["src/com/android/systemui/EventLogTags.logtags"], } +java_library { + name: "SystemUI-flags", + srcs: [ + "src/com/android/systemui/flags/Flags.java", + ], + libs: [ + "SystemUI-flag-types", + ], + static_kotlin_stdlib: false, +} + filegroup { name: "ReleaseJavaFiles", - srcs: ["src/com/android/systemui/flags/FeatureFlagManager.java"], + srcs: [ + "src-release/**/*.kt", + "src-release/**/*.java", + ], } filegroup { name: "DebugJavaFiles", - srcs: ["src-debug/com/android/systemui/flags/FeatureFlagManager.java"], + srcs: [ + "src-debug/**/*.kt", + "src-debug/**/*.java", + ], } android_library { @@ -66,6 +83,7 @@ android_library { "src/**/*.kt", "src/**/*.java", "src/**/I*.aidl", + ":ReleaseJavaFiles", ], product_variables: { debuggable: { @@ -108,6 +126,7 @@ android_library { "iconloader_base", "SystemUI-tags", "SystemUI-proto", + "SystemUI-flags", "monet", "dagger2", "jsr330", @@ -159,6 +178,7 @@ android_library { "src/**/*.kt", "src/**/*.java", "src/**/I*.aidl", + ":ReleaseJavaFiles", ], static_libs: [ "SystemUIAnimationLib", diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index e509777633e7..1fe509ac538f 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -116,13 +116,18 @@ ], "auto-end-to-end-postsubmit": [ { - "name": "AndroidAutoUiTests", + "name": "AndroidAutomotiveHomeTests", "options" : [ { - "include-filter": "android.test.functional.auto.apps.HomeHelperTest" - }, + "include-filter": "android.platform.tests.HomeTest" + } + ] + }, + { + "name": "AndroidAutomotiveNotificationsTests", + "options" : [ { - "include-filter": "android.test.functional.auto.apps.NotificationHelperTest" + "include-filter": "android.platform.tests.NotificationTest" } ] } 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 669a054eaa2a..865f96be1775 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -38,9 +38,6 @@ private const val TAG = "DialogLaunchAnimator" /** * A class that allows dialogs to be started in a seamless way from a view that is transforming * nicely into the starting dialog. - * - * Important: Don't forget to call [DialogLaunchAnimator.onDozeAmountChanged] when the doze amount - * changes to gracefully handle dialogs fading out when the device is dozing. */ class DialogLaunchAnimator( private val context: Context, @@ -89,8 +86,17 @@ class DialogLaunchAnimator( // host dialog. if (dialog is ListenableDialog) { dialog.addListener(object : DialogListener { - override fun onDismiss() { + override fun onDismiss(reason: DialogListener.DismissReason) { dialog.removeListener(this) + + // We disable the exit animation if we are dismissing the dialog because the + // device is being locked, otherwise the animation looks bad if AOD is enabled. + // If AOD is disabled the screen will directly becomes black and we won't see + // the animation anyways. + if (reason == DialogListener.DismissReason.DEVICE_LOCKED) { + launchAnimation.exitAnimationDisabled = true + } + hostDialog.dismiss() } @@ -110,6 +116,10 @@ class DialogLaunchAnimator( launchAnimation.ignoreNextCallToHide = true dialog.hide() } + + override fun onSizeChanged() { + launchAnimation.onOriginalDialogSizeChanged() + } }) } @@ -117,13 +127,6 @@ class DialogLaunchAnimator( return hostDialog } - /** Notify the current doze amount, to ensure that dialogs fade out when dozing. */ - // TODO(b/193634619): Replace this by some mandatory constructor parameter to make sure that we - // don't forget to call this when the doze amount changes. - fun onDozeAmountChanged(amount: Float) { - currentAnimations.forEach { it.onDozeAmountChanged(amount) } - } - /** * Ensure that all dialogs currently shown won't animate into their touch surface when * dismissed. @@ -147,6 +150,7 @@ interface HostDialogProvider { * 2. call [dismissOverride] instead of doing any dismissing logic. The actual dismissing * logic should instead be done inside the lambda passed to [dismissOverride], which will * be called after the exit animation. + * 3. Be full screen, i.e. have a window matching its parent size. * * See SystemUIHostDialogProvider for an example of implementation. */ @@ -168,14 +172,25 @@ interface ListenableDialog { } interface DialogListener { + /** The reason why a dialog was dismissed. */ + enum class DismissReason { + UNKNOWN, + + /** The device was locked, which dismissed this dialog. */ + DEVICE_LOCKED, + } + /** Called when this dialog dismiss() is called. */ - fun onDismiss() + fun onDismiss(reason: DismissReason) /** Called when this dialog hide() is called. */ fun onHide() /** Called when this dialog show() is called. */ fun onShow() + + /** Called when this dialog size might have changed, e.g. because of configuration changes. */ + fun onSizeChanged() } private class DialogLaunchAnimation( @@ -254,10 +269,6 @@ private class DialogLaunchAnimation( val window = hostDialog.window ?: throw IllegalStateException("There is no window associated to the host dialog") window.setBackgroundDrawableResource(android.R.color.transparent) - window.setLayout( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.MATCH_PARENT - ) // If we are using gesture navigation, then we can overlay the navigation/task bars with // the host dialog. @@ -425,6 +436,19 @@ private class DialogLaunchAnimation( }) } + fun onOriginalDialogSizeChanged() { + // The dialog is the single child of the root. + if (hostDialogRoot.childCount != 1) { + return + } + + val dialogView = hostDialogRoot.getChildAt(0) + val layoutParams = dialogView.layoutParams as? FrameLayout.LayoutParams ?: return + layoutParams.width = originalDialog.window.attributes.width + layoutParams.height = originalDialog.window.attributes.height + dialogView.layoutParams = layoutParams + } + private fun maybeStartLaunchAnimation() { if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) { return @@ -638,14 +662,4 @@ private class DialogLaunchAnimation( return (touchSurface.parent as? View)?.isShown ?: true } - - internal fun onDozeAmountChanged(amount: Float) { - val alpha = Interpolators.ALPHA_OUT.getInterpolation(1 - amount) - val decorView = this.hostDialog.window?.decorView ?: return - if (decorView.hasOverlappingRendering() && alpha > 0.0f && - alpha < 1.0f && decorView.layerType != View.LAYER_TYPE_HARDWARE) { - decorView.setLayerType(View.LAYER_TYPE_HARDWARE, null) - } - decorView.alpha = alpha - } } diff --git a/packages/SystemUI/docs/plugins.md b/packages/SystemUI/docs/plugins.md index 689200577aad..378cba5155ab 100644 --- a/packages/SystemUI/docs/plugins.md +++ b/packages/SystemUI/docs/plugins.md @@ -1,3 +1,4 @@ + # SystemUI Plugins Plugins provide an easy way to rapidly prototype SystemUI features. Plugins are APKs that will be installable only on Build.IS_DEBUGGABLE (dogfood) builds, that can change the behavior of SystemUI at runtime. This is done by creating a basic set of interfaces that the plugins can expect to be in SysUI, then the portion of code controlled by the interface can be iterated on faster than currently. diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java index 883f4de1149c..94fdbae83253 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -183,12 +183,6 @@ public interface NotificationMenuRowPlugin extends Plugin { public boolean canBeDismissed(); /** - * Informs the menu whether dismiss gestures are left-to-right or right-to-left. - */ - default void setDismissRtl(boolean dismissRtl) { - } - - /** * Determines whether the menu should remain open given its current state, or snap closed. * @return true if the menu should remain open, false otherwise. */ diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml index 7f0f68f4fc06..c58e2e3266d0 100644 --- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml +++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml @@ -37,7 +37,7 @@ android:id="@+id/locked_fp" android:state_middle="true" android:state_single="false" - android:drawable="@drawable/ic_fingerprint" /> + android:drawable="@drawable/ic_kg_fingerprint" /> <item android:id="@+id/unlocked" diff --git a/packages/SystemUI/res/drawable/ic_fingerprint.xml b/packages/SystemUI/res/drawable/ic_kg_fingerprint.xml index 91d72a7902c0..91d72a7902c0 100644 --- a/packages/SystemUI/res/drawable/ic_fingerprint.xml +++ b/packages/SystemUI/res/drawable/ic_kg_fingerprint.xml diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml new file mode 100644 index 000000000000..f0b59d825417 --- /dev/null +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/split_shade_status_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="@dimen/split_shade_header_min_height" + android:clickable="false" + android:focusable="true" + android:paddingLeft="@dimen/qs_panel_padding" + android:paddingRight="@dimen/qs_panel_padding" + android:visibility="gone" + android:theme="@style/Theme.SystemUI.QuickSettings.Header"> + + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/center" + app:layout_constraintGuide_percent="0.5" + android:orientation="vertical" /> + + <com.android.systemui.statusbar.policy.Clock + android:id="@+id/clock" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintHeight_min="@dimen/split_shade_header_min_height" + android:gravity="start|center_vertical" + android:paddingStart="@dimen/status_bar_left_clock_starting_padding" + android:paddingEnd="@dimen/status_bar_left_clock_end_padding" + android:singleLine="true" + android:textAppearance="@style/TextAppearance.QS.Status" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/date" + /> + + <com.android.systemui.statusbar.policy.DateView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintHeight_min="@dimen/split_shade_header_min_height" + android:layout_gravity="start|center_vertical" + android:gravity="center_vertical" + android:singleLine="true" + android:textAppearance="@style/TextAppearance.QS.Status" + app:datePattern="@string/abbrev_wday_month_day_no_year_alarm" + app:layout_constraintStart_toEndOf="@id/clock" + app:layout_constraintEnd_toStartOf="@id/carrier_group" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="0" + /> + + <include + android:id="@+id/carrier_group" + layout="@layout/qs_carrier_group" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintHeight_min="@dimen/split_shade_header_min_height" + app:layout_constrainedWidth="true" + android:layout_gravity="end|center_vertical" + android:layout_marginStart="8dp" + android:focusable="false" + android:minHeight="@dimen/split_shade_header_min_height" + android:minWidth="48dp" + app:layout_constraintStart_toEndOf="@id/date" + app:layout_constraintEnd_toStartOf="@id/statusIcons" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + + <com.android.systemui.statusbar.phone.StatusIconContainer + android:id="@+id/statusIcons" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintHeight_min="@dimen/split_shade_header_min_height" + android:paddingEnd="@dimen/signal_cluster_battery_padding" + app:layout_constraintStart_toEndOf="@id/carrier_group" + app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + /> + + <com.android.systemui.battery.BatteryMeterView + android:id="@+id/batteryRemainingIcon" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintHeight_min="@dimen/split_shade_header_min_height" + app:textAppearance="@style/TextAppearance.QS.Status" + app:layout_constraintStart_toEndOf="@id/statusIcons" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index f4faa62430db..86e2661f9534 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -329,7 +329,8 @@ android:layout_height="wrap_content" android:paddingBottom="4dp" android:clickable="false" - android:focusable="false"> + android:focusable="false" + android:visibility="gone"> <LinearLayout android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml index 5389d9bbcc97..c949ba0db171 100644 --- a/packages/SystemUI/res/layout/ongoing_call_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml @@ -21,6 +21,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical|start" + android:layout_marginStart="5dp" > <LinearLayout android:id="@+id/ongoing_call_chip_background" diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml index 1c3eedba4f6f..2fb775cbc9be 100644 --- a/packages/SystemUI/res/layout/rotate_suggestion.xml +++ b/packages/SystemUI/res/layout/rotate_suggestion.xml @@ -18,15 +18,13 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - - <com.android.systemui.navigationbar.buttons.KeyButtonView + <com.android.systemui.shared.rotation.FloatingRotationButtonView android:id="@+id/rotate_suggestion" android:layout_width="@dimen/floating_rotation_button_diameter" android:layout_height="@dimen/floating_rotation_button_diameter" - android:contentDescription="@string/accessibility_rotate_button" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" android:layout_gravity="bottom|left" android:scaleType="center" android:visibility="invisible" /> -</FrameLayout>
\ No newline at end of file +</FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index cc1af873ce2b..82186c13394e 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -75,7 +75,11 @@ android:clipToPadding="false" android:clipChildren="false"> - <include layout="@layout/split_shade_header"/> + <ViewStub + android:id="@+id/qs_header_stub" + android:layout_height="wrap_content" + android:layout_width="match_parent" + /> <include layout="@layout/keyguard_status_view" diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 0873a4f9c001..083e3e48f514 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 toestel gekies"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> toestelle gekies"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ontkoppel)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ontkoppel)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kon nie koppel nie. Probeer weer."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Bind nuwe toestel saam"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Bounommer"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 579b5d278588..e68a6f579497 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ቡድን"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 መሣሪያ ተመርጧል"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> መሣሪያዎች ተመርጠዋል"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ግንኙነት ተቋርጧል)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ተቋርጧል)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ማገናኘት አልተቻለም። እንደገና ይሞክሩ።"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"አዲስ መሣሪያ ያጣምሩ"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"የግንብ ቁጥር"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 5875d2b9ebb0..60e1f06ba470 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -349,7 +349,7 @@ <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"سماعات الأذن الطبية"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"جارٍ التفعيل…"</string> <string name="quick_settings_brightness_label" msgid="680259653088849563">"السطوع"</string> - <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"تدوير تلقائي"</string> + <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"التدوير التلقائي"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"التدوير التلقائي للشاشة"</string> <string name="accessibility_quick_settings_rotation_value" msgid="2916484894750819251">"وضع <xliff:g id="ID_1">%s</xliff:g>"</string> <string name="quick_settings_rotation_locked_label" msgid="4420863550666310319">"تم قفل التدوير"</string> @@ -1066,7 +1066,7 @@ <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"نقله إلى الحافة وإخفاؤه"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"نقله إلى خارج الحافة وإظهاره"</string> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"إيقاف/تفعيل"</string> - <string name="quick_controls_title" msgid="6839108006171302273">"أدوات التحكم بالأجهزة"</string> + <string name="quick_controls_title" msgid="6839108006171302273">"التحكم بالجهاز"</string> <string name="controls_providers_title" msgid="6879775889857085056">"اختيار تطبيق لإضافة عناصر التحكّم"</string> <plurals name="controls_number_of_favorites" formatted="false" msgid="1057347832073807380"> <item quantity="zero">تمت إضافة <xliff:g id="NUMBER_1">%s</xliff:g> عنصر تحكّم.</item> @@ -1135,7 +1135,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"مجموعة"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"تم اختيار جهاز واحد."</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"تم اختيار <xliff:g id="COUNT">%1$d</xliff:g> جهاز."</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (غير متّصل)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(غير متّصل)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"تعذّر الاتصال. يُرجى إعادة المحاولة."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"إقران جهاز جديد"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"رقم الإصدار"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index d6d2900b8d1c..fc53a1082f80 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -235,7 +235,7 @@ <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"বেটাৰী চাৰ্জ হৈ আছে, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> শতাংশ।"</string> <string name="accessibility_settings_button" msgid="2197034218538913880">"ছিষ্টেমৰ ছেটিং৷"</string> <string name="accessibility_notifications_button" msgid="3960913924189228831">"জাননীসমূহ।"</string> - <string name="accessibility_overflow_action" msgid="8555835828182509104">"সকলো জাননীবোৰ চাওক"</string> + <string name="accessibility_overflow_action" msgid="8555835828182509104">"আটাইবোৰ জাননী চাওক"</string> <string name="accessibility_remove_notification" msgid="1641455251495815527">"জাননী মচক৷"</string> <string name="accessibility_gps_enabled" msgid="4061313248217660858">"জিপিএছ সক্ষম হ\'ল৷"</string> <string name="accessibility_gps_acquiring" msgid="896207402196024040">"জিপিএছ বিচাৰি থকা হৈছে।"</string> @@ -311,7 +311,7 @@ <string name="gps_notification_found_text" msgid="3145873880174658526">"জিপিএছএ অৱস্থান ছেট কৰিছে"</string> <string name="accessibility_location_active" msgid="2845747916764660369">"অৱস্থানৰ অনুৰোধ সক্ৰিয় হৈ আছে"</string> <string name="accessibility_sensors_off_active" msgid="2619725434618911551">"ছেন্সৰ অফ সক্ৰিয় কৰা আছে"</string> - <string name="accessibility_clear_all" msgid="970525598287244592">"সকলো জাননী মচক৷"</string> + <string name="accessibility_clear_all" msgid="970525598287244592">"আটাইবোৰ জাননী মচক৷"</string> <string name="notification_group_overflow_indicator" msgid="7605120293801012648">"+ <xliff:g id="NUMBER">%s</xliff:g>"</string> <plurals name="notification_group_overflow_description" formatted="false" msgid="91483442850649192"> <item quantity="one"> ভিতৰত আৰু <xliff:g id="NUMBER_1">%s</xliff:g>টা জাননী আছে।</item> @@ -448,8 +448,8 @@ <string name="zen_priority_introduction" msgid="3159291973383796646">"আপুনি নিৰ্দিষ্ট কৰা এলাৰ্ম, ৰিমাইণ্ডাৰ, ইভেন্ট আৰু কল কৰোঁতাৰ বাহিৰে আন কোনো শব্দৰ পৰা আপুনি অসুবিধা নাপাব। কিন্তু, সংগীত, ভিডিঅ\' আৰু খেলসমূহকে ধৰি আপুনি প্লে কৰিব খোজা যিকোনো বস্তু তথাপি শুনিব পাৰিব।"</string> <string name="zen_alarms_introduction" msgid="3987266042682300470">"আপুনি নিৰ্দিষ্ট কৰা এলাৰ্মৰ বাহিৰে আন কোনো ধ্বনি আৰু কম্পনৰ পৰা আপুনি অসুবিধা নাপাব। কিন্তু, সংগীত, ভিডিঅ\' আৰু খেলসমূহকে ধৰি আপুনি প্লে কৰিব খোজা যিকোনো বস্তু তথাপি শুনিব পাৰিব।"</string> <string name="zen_priority_customize_button" msgid="4119213187257195047">"নিজৰ উপযোগিতা অনুসৰি"</string> - <string name="zen_silence_introduction_voice" msgid="853573681302712348">"এই কার্যই এলার্ম, সংগীত, ভিডিঅ\' আৰু খেলসমূহকে ধৰি সকলোৰে বাবে ধ্বনি আৰু কম্পন অৱৰোধ কৰিব। আপুনি ফ\'ন কল তথাপি কৰিবলৈ সক্ষম হ\'ব।"</string> - <string name="zen_silence_introduction" msgid="6117517737057344014">"এই কার্যই এলার্ম, মিউজিক, ভিডিঅ\' আৰু গেইমকে ধৰি সকলোৰে ধ্বনি আৰু কম্পন অৱৰোধ কৰে।"</string> + <string name="zen_silence_introduction_voice" msgid="853573681302712348">"এই কার্যই এলার্ম, সংগীত, ভিডিঅ\' আৰু খেলসমূহকে ধৰি আটাইবোৰৰ বাবে ধ্বনি আৰু কম্পন অৱৰোধ কৰিব। আপুনি ফ\'ন কল তথাপি কৰিবলৈ সক্ষম হ\'ব।"</string> + <string name="zen_silence_introduction" msgid="6117517737057344014">"এই কার্যই এলার্ম, মিউজিক, ভিডিঅ\' আৰু গেইমকে ধৰি আটাইবোৰৰ ধ্বনি আৰু কম্পন অৱৰোধ কৰে।"</string> <string name="keyguard_more_overflow_text" msgid="5819512373606638727">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string> <string name="speed_bump_explanation" msgid="7248696377626341060">"কম জৰুৰী জাননীসমূহ তলত"</string> <string name="notification_tap_again" msgid="4477318164947497249">"খুলিবলৈ পুনৰাই টিপক"</string> @@ -482,7 +482,7 @@ <string name="user_add_user" msgid="4336657383006913022">"ব্যৱহাৰকাৰী যোগ কৰক"</string> <string name="user_new_user_name" msgid="2019166282704195789">"নতুন ব্যৱহাৰকাৰী"</string> <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"অতিথি আঁতৰাবনে?"</string> - <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ সকলো এপ্ আৰু ডেটা মচা হ\'ব।"</string> + <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string> <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"আঁতৰাওক"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"অতিথি, আপোনাক পুনৰ স্বাগতম!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"আপুনি আপোনাৰ ছেশ্বন অব্যাহত ৰাখিব বিচাৰেনে?"</string> @@ -502,24 +502,24 @@ <item quantity="other">আপুনি <xliff:g id="COUNT">%d</xliff:g> জনলৈকে ব্যৱহাৰকাৰী যোগ কৰিব পাৰে।</item> </plurals> <string name="user_remove_user_title" msgid="9124124694835811874">"ব্যৱহাৰকাৰীক আঁতৰাবনে?"</string> - <string name="user_remove_user_message" msgid="6702834122128031833">"এই ব্যৱহাৰকাৰীৰ সকলো এপ্ আৰু ডেটা মচা হ\'ব।"</string> + <string name="user_remove_user_message" msgid="6702834122128031833">"এই ব্যৱহাৰকাৰীৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string> <string name="user_remove_user_remove" msgid="8387386066949061256">"আঁতৰাওক"</string> <string name="battery_saver_notification_title" msgid="8419266546034372562">"বেটাৰী সঞ্চয়কাৰী অন হৈ আছে"</string> <string name="battery_saver_notification_text" msgid="2617841636449016951">"কাৰ্যদক্ষতা আৰু নেপথ্য ডেটা হ্ৰাস কৰে"</string> <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"বেটাৰী সঞ্চয়কাৰী অফ কৰক"</string> - <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>এ আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিঙৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা সকলো তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string> - <string name="media_projection_dialog_service_text" msgid="958000992162214611">"এই সুবিধাটো প্ৰদান কৰা সেৱাটোৱে আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিংৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা সকলো তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string> + <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>এ আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিঙৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা আটাইবোৰ তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string> + <string name="media_projection_dialog_service_text" msgid="958000992162214611">"এই সুবিধাটো প্ৰদান কৰা সেৱাটোৱে আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিঙৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা আটাইবোৰ তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ জৰিয়তে ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে ?"</string> <string name="media_projection_remember_text" msgid="6896767327140422951">"পুনৰাই নেদেখুৱাব"</string> - <string name="clear_all_notifications_text" msgid="348312370303046130">"সকলো মচক"</string> + <string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string> <string name="notification_section_header_incoming" msgid="850925217908095197">"নতুন"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"নীৰৱ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"জাননীসমূহ"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"বাৰ্তালাপ"</string> - <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"সকলো নীৰৱ জাননী মচক"</string> + <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"আটাইবোৰ নীৰৱ জাননী মচক"</string> <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"অসুবিধা নিদিব-ই জাননী পজ কৰিছে"</string> <string name="media_projection_action_text" msgid="3634906766918186440">"এতিয়াই আৰম্ভ কৰক"</string> <string name="empty_shade_text" msgid="8935967157319717412">"কোনো জাননী নাই"</string> @@ -704,7 +704,7 @@ <string name="enable_bluetooth_message" msgid="6740938333772779717">"আপোনাৰ টেবলেটত আপোনাৰ কীব\'ৰ্ড সংযোগ কৰিবলৈ আপুনি প্ৰথমে ব্লুটুথ অন কৰিব লাগিব।"</string> <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"অন কৰক"</string> <string name="show_silently" msgid="5629369640872236299">"জাননীসমূহ নীৰৱে দেখুৱাওক"</string> - <string name="block" msgid="188483833983476566">"সকলো জাননী অৱৰোধ কৰক"</string> + <string name="block" msgid="188483833983476566">"আটাইবোৰ জাননী অৱৰোধ কৰক"</string> <string name="do_not_silence" msgid="4982217934250511227">"নীৰৱ নকৰিব"</string> <string name="do_not_silence_block" msgid="4361847809775811849">"নীৰৱ অথবা অৱৰোধ নকৰিব"</string> <string name="tuner_full_importance_settings" msgid="1388025816553459059">"জাননী নিয়ন্ত্ৰণৰ অধিক কৰ্তৃত্ব"</string> @@ -754,7 +754,7 @@ <string name="notification_unblockable_desc" msgid="2073030886006190804">"এই জাননীসমূহ সংশোধন কৰিব নোৱাৰি।"</string> <string name="notification_multichannel_desc" msgid="7414593090056236179">"এই ধৰণৰ জাননীবোৰ ইয়াত কনফিগাৰ কৰিব পৰা নাযায়"</string> <string name="notification_delegate_header" msgid="1264510071031479920">"প্ৰক্সি হিচাপে পঠিওৱা জাননী"</string> - <string name="notification_channel_dialog_title" msgid="6856514143093200019">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ সকলো জাননী"</string> + <string name="notification_channel_dialog_title" msgid="6856514143093200019">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ আটাইবোৰ জাননী"</string> <string name="see_more_title" msgid="7409317011708185729">"অধিক চাওক"</string> <string name="appops_camera" msgid="5215967620896725715">"এই এপে কেমেৰা ব্য়ৱহাৰ কৰি আছে।"</string> <string name="appops_microphone" msgid="8805468338613070149">"এই এপে মাইক্ৰ\'ফ\'ন ব্য়ৱহাৰ কৰি আছে।"</string> @@ -1062,7 +1062,7 @@ <string name="controls_favorite_default_title" msgid="967742178688938137">"নিয়ন্ত্ৰণসমূহ"</string> <string name="controls_favorite_subtitle" msgid="6481675111056961083">"ক্ষিপ্ৰ ছেটিঙৰ পৰা এক্সেছ কৰিবলৈ নিয়ন্ত্ৰণসমূহ বাছনি কৰক"</string> <string name="controls_favorite_rearrange" msgid="5616952398043063519">"নিয়ন্ত্ৰণসমূহ পুনৰ সজাবলৈ ধৰি ৰাখক আৰু টানি আনি এৰক"</string> - <string name="controls_favorite_removed" msgid="5276978408529217272">"সকলো নিয়ন্ত্ৰণ আঁতৰোৱা হৈছে"</string> + <string name="controls_favorite_removed" msgid="5276978408529217272">"আটাইবোৰ নিয়ন্ত্ৰণ আঁতৰোৱা হৈছে"</string> <string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"সালসলনিসমূহ ছেভ নহ’ল"</string> <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"অন্য এপ্সমূহ চাওক"</string> <string name="controls_favorite_load_error" msgid="5126216176144877419">"নিয়ন্ত্ৰণসমূহ ল’ড কৰিবপৰা নগ’ল। এপ্টোৰ ছেটিং সলনি কৰা হোৱা নাই বুলি নিশ্চিত কৰিবলৈ <xliff:g id="APP">%s</xliff:g> এপ্টো পৰীক্ষা কৰক।"</string> @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"গোট"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"১ টা ডিভাইচ বাছনি কৰা হৈছে"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> টা ডিভাইচ বাছনি কৰা হৈছে"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (সংযোগ বিচ্ছিন্ন হৈছে)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(সংযোগ বিচ্ছিন্ন কৰা হৈছে)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"সংযোগ কৰিব পৰা নগ’ল। পুনৰ চেষ্টা কৰক।"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"নতুন ডিভাইচ পেয়াৰ কৰক"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ডৰ নম্বৰ"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index a23283437a7f..1f1997fdaf6b 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Qrup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçilib"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> cihaz seçilib"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (bağlantı kəsilib)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(bağlantı kəsildi)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Qoşulmaq alınmadı. Yenə cəhd edin."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Cihaz əlavə edin"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Montaj nömrəsi"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index caab5543aded..ee82b070f473 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -1117,7 +1117,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izabran je 1 uređaj"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Izabranih uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (veza je prekinuta)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(veza je prekinuta)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije uspelo. Probajte ponovo."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Upari novi uređaj"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string> @@ -1161,7 +1161,7 @@ <string name="person_available" msgid="2318599327472755472">"Dostupno"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem sa očitavanjem merača baterije"</string> <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string> - <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm nije podešen"</string> + <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nije podešen"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor za otisak prsta"</string> <string name="accessibility_udfps_disabled_button" msgid="4284034245130239384">"Senzor za otisak prsta je onemogućen"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"potvrdite identitet"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index d327b4deb4e2..58dbe0e180db 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрана 1 прылада"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Выбрана прылад: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (адключана)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(адключана)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не ўдалося падключыцца. Паўтарыце спробу."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Спалучыць з новай прыладай"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Нумар зборкі"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 42b80f4aceae..8968dab785fe 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 избрано устройство"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> избрани устройства"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (връзката е прекратена)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(връзката е прекратена)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Неуспешно свързване. Опитайте отново."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Сдвояване на ново устройство"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер на компилацията"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 5b8c662213af..3478b4d90431 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"গ্রুপ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"১টি ডিভাইস বেছে নেওয়া হয়েছে"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g>টি ডিভাইস বেছে নেওয়া হয়েছে"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (কানেক্ট করা নেই)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ডিসকানেক্ট হয়ে গেছে)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"কানেক্ট করা যায়নি। আবার চেষ্টা করুন।"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"নতুন ডিভাইস পেয়ার করুন"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ড নম্বর"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 19e868f5e6ea..84e806f17b3d 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -1117,7 +1117,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je 1 uređaj"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Broj odabranih uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (veza je prekinuta)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(veza je prekinuta)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije uspjelo. Pokušajte ponovo."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Uparite novi uređaj"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index f31a72dad3a3..8dedb28b23eb 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositiu seleccionat"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"S\'han seleccionat <xliff:g id="COUNT">%1$d</xliff:g> dispositius"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconnectat)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desconnectat)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No s\'ha pogut connectar. Torna-ho a provar."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincula un dispositiu nou"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilació"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 05e4b1ec0207..fbef25aa7800 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Je vybráno 1 zařízení"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Vybraná zařízení: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (odpojeno)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(odpojeno)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Spojení se nezdařilo. Zkuste to znovu."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Spárovat nové zařízení"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo sestavení"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 8145ffa2de46..72771df230a3 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Der er valgt 1 enhed"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Der er valgt <xliff:g id="COUNT">%1$d</xliff:g> enhed"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ingen forbindelse)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(afbrudt)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Der kunne ikke oprettes forbindelse. Prøv igen."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Par ny enhed"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildnummer"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index fc36c84914f8..933de430bdb3 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ein Gerät ausgewählt"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> Geräte ausgewählt"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nicht verbunden)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(nicht verbunden)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Verbindung nicht möglich. Versuch es noch einmal."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Neues Gerät koppeln"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build-Nummer"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index a93fec5d25f6..bd197608cd07 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Ομάδα"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Επιλέχτηκε 1 συσκευή"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Επιλέχτηκαν <xliff:g id="COUNT">%1$d</xliff:g> συσκευές"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (αποσυνδέθηκε)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(αποσυνδέθηκε)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Δεν ήταν δυνατή η σύνδεση. Δοκιμάστε ξανά."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Σύζευξη νέας συσκευής"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Αριθμός έκδοσης"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 2f4a17d07b62..8f8f195a2a71 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index e1ab43a00da4..5532dcb84da0 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 2f4a17d07b62..8f8f195a2a71 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 2f4a17d07b62..8f8f195a2a71 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 7ddbdd69850b..1b538a7bf9ce 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device selected"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 95cd9ba1b13d..08d0a0523b6d 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -807,7 +807,7 @@ <string name="battery_detail_switch_title" msgid="6940976502957380405">"Ahorro de batería"</string> <string name="battery_detail_switch_summary" msgid="3668748557848025990">"Reduce el rendimiento y el uso de datos en segundo plano"</string> <string name="keyboard_key_button_template" msgid="8005673627272051429">"Botón <xliff:g id="NAME">%1$s</xliff:g>"</string> - <string name="keyboard_key_home" msgid="3734400625170020657">"Página principal"</string> + <string name="keyboard_key_home" msgid="3734400625170020657">"Inicio"</string> <string name="keyboard_key_back" msgid="4185420465469481999">"Atrás"</string> <string name="keyboard_key_dpad_up" msgid="2164184320424941416">"Arriba"</string> <string name="keyboard_key_dpad_down" msgid="2110172278574325796">"Abajo"</string> @@ -827,7 +827,7 @@ <string name="keyboard_key_page_up" msgid="173914303254199845">"Re Pág"</string> <string name="keyboard_key_page_down" msgid="9035902490071829731">"Av Pág"</string> <string name="keyboard_key_forward_del" msgid="5325501825762733459">"Borrar"</string> - <string name="keyboard_key_move_home" msgid="3496502501803911971">"Página principal"</string> + <string name="keyboard_key_move_home" msgid="3496502501803911971">"Inicio"</string> <string name="keyboard_key_move_end" msgid="99190401463834854">"Fin"</string> <string name="keyboard_key_insert" msgid="4621692715704410493">"Insertar"</string> <string name="keyboard_key_num_lock" msgid="7209960042043090548">"Bloqueo numérico"</string> @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Se seleccionó 1 dispositivo"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Se seleccionaron <xliff:g id="COUNT">%1$d</xliff:g> dispositivos"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desconectado)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No se pudo establecer la conexión. Vuelve a intentarlo."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincular dispositivo nuevo"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 15af90396cba..eda51baac58f 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo seleccionado"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos seleccionados"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desconectado)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No se ha podido conectar. Inténtalo de nuevo."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Emparejar nuevo dispositivo"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 39d29cc4fc98..e67462791bbd 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -345,7 +345,7 @@ <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Kuuldeaparaadid"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Sisselülitamine …"</string> <string name="quick_settings_brightness_label" msgid="680259653088849563">"Heledus"</string> - <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automaatne pööramine"</string> + <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. pööramine"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Kuva automaatne pööramine"</string> <string name="accessibility_quick_settings_rotation_value" msgid="2916484894750819251">"Režiim <xliff:g id="ID_1">%s</xliff:g>"</string> <string name="quick_settings_rotation_locked_label" msgid="4420863550666310319">"Pööramine on lukustatud"</string> @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 seade on valitud"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> seadet on valitud"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (pole ühendatud)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ühendus on katkestatud)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ühenduse loomine ebaõnnestus. Proovige uuesti."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Uue seadme sidumine"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Järgunumber"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index bacf6efa74cd..5f3a2863e1b1 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Taldea"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 gailu hautatu da"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> gailu hautatu dira"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (deskonektatuta)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(deskonektatuta)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ezin izan da konektatu. Saiatu berriro."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parekatu beste gailu batekin"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Konpilazio-zenbakia"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 9a9c20f2eb5d..6cd43497a14b 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"گروه"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"۱ دستگاه انتخاب شد"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> دستگاه انتخاب شد"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (اتصال قطع شد)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(اتصال قطع شد)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"متصل نشد. دوباره امتحان کنید."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"مرتبط کردن دستگاه جدید"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"شماره ساخت"</string> @@ -1155,7 +1155,7 @@ <string name="person_available" msgid="2318599327472755472">"دردسترس"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"مشکلی در خواندن میزان باتری وجود دارد"</string> <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"برای اطلاعات بیشتر ضربه بزنید"</string> - <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"هشداری تنظیم نشده است"</string> + <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"هشداری تنظیم نشده"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"حسگر اثرانگشت"</string> <string name="accessibility_udfps_disabled_button" msgid="4284034245130239384">"حسگر اثر انگشت غیرفعال است"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"اصالتسنجی کردن"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index ca0f4525a40d..c0dc8a070ec1 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -1046,7 +1046,7 @@ <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Siirrä reunaan ja piilota"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Siirrä pois reunasta ja näytä"</string> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"vaihda"</string> - <string name="quick_controls_title" msgid="6839108006171302273">"Laitteiden hallinta"</string> + <string name="quick_controls_title" msgid="6839108006171302273">"Laitehallinta"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Valitse sovellus lisätäksesi säätimiä"</string> <plurals name="controls_number_of_favorites" formatted="false" msgid="1057347832073807380"> <item quantity="other"><xliff:g id="NUMBER_1">%s</xliff:g> säädintä lisätty</item> @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Ryhmä"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 laite valittu"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> laitetta valittu"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (yhteys katkaistu)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(yhteys katkaistu)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ei yhteyttä. Yritä uudelleen."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Muodosta uusi laitepari"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Koontiversion numero"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index ef4f86dc4b71..675c30c6449a 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Un appareil sélectionné"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> appareil sélectionné"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (déconnecté)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(déconnecté)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossible de se connecter. Réessayez."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Associer un autre appareil"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de version"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 3d6dd559855a..e3d6ef9fa5d1 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 appareil sélectionné"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> appareils sélectionnés"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (déconnecté)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(déconnecté)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossible de se connecter. Réessayez."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Associer un nouvel appareil"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de build"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 8570e9a6b52b..e12796a58e90 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Seleccionouse 1 dispositivo"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Seleccionáronse <xliff:g id="COUNT">%1$d</xliff:g> dispositivos"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (dispositivo desconectado)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desconectado)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Non se puido establecer a conexión. Téntao de novo."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincular dispositivo novo"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 95efbad63afe..e81fedb8181c 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ગ્રૂપ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ડિવાઇસ પસંદ કર્યું"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ડિવાઇસ પસંદ કર્યા"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ડિસ્કનેક્ટ થયેલું)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ડિસ્કનેક્ટ કરેલું)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"કનેક્ટ કરી શકાયું નહીં. ફરી પ્રયાસ કરો."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"નવા ડિવાઇસ સાથે જોડાણ કરો"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"બિલ્ડ નંબર"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 2a6475566a69..d344f3e5cd96 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ग्रुप"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिवाइस चुना गया"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> डिवाइस चुने गए"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिसकनेक्ट किया गया)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(डिसकनेक्ट हो गया)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट नहीं किया जा सका. फिर से कोशिश करें."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नया डिवाइस जोड़ें"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 3edd928b9488..59179e627eb5 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -346,7 +346,7 @@ <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni aparati"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string> <string name="quick_settings_brightness_label" msgid="680259653088849563">"Svjetlina"</string> - <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko zakretanje"</string> + <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. zakretanje"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko zakretanje zaslona"</string> <string name="accessibility_quick_settings_rotation_value" msgid="2916484894750819251">"Način: <xliff:g id="ID_1">%s</xliff:g>"</string> <string name="quick_settings_rotation_locked_label" msgid="4420863550666310319">"Izmjenjivanje je zaključano"</string> @@ -1117,7 +1117,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je jedan uređaj"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Odabrano uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nije povezano)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(nije povezano)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije bilo moguće. Pokušajte ponovo."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Uparite novi uređaj"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj međuverzije"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 34858083c04a..f1b694ee9342 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Csoport"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 eszköz kiválasztva"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> eszköz kiválasztva"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (leválasztva)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(leválasztva)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Sikertelen csatlakozás. Próbálja újra."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Új eszköz párosítása"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildszám"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index fde4d32ad87b..7e62a823329c 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Խումբ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ընտրված է 1 սարք"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Ընտրված է <xliff:g id="COUNT">%1$d</xliff:g> սարք"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (անջատված է)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(անջատված է)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Չհաջողվեց միանալ։ Նորից փորձեք։"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Նոր սարքի զուգակցում"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Կառուցման համարը"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index f3bf7dfc0448..fcb81ba450de 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 perangkat dipilih"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> perangkat dipilih"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (terputus)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(terputus)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tidak dapat terhubung. Coba lagi."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sambungkan perangkat baru"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nomor build"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 7cd5444f2a09..52d45fee0fa8 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Hópur"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 tæki valið"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> tæki valin"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (aftengt)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(aftengt)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tenging mistókst. Reyndu aftur."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Para nýtt tæki"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Útgáfunúmer smíðar"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 496b2871eac4..b4c936570301 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -516,7 +516,7 @@ <string name="manage_notifications_text" msgid="6885645344647733116">"Gestisci"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Cronologia"</string> <string name="notification_section_header_incoming" msgid="850925217908095197">"Nuove"</string> - <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenziose"</string> + <string name="notification_section_header_gentle" msgid="6804099527336337197">"Notifiche silenziose"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifiche"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversazioni"</string> <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Cancella tutte le notifiche silenziose"</string> @@ -1046,7 +1046,7 @@ <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Sposta fino a bordo e nascondi"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Sposta fuori da bordo e mostra"</string> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"attiva/disattiva"</string> - <string name="quick_controls_title" msgid="6839108006171302273">"Controllo dei dispositivi"</string> + <string name="quick_controls_title" msgid="6839108006171302273">"Controllo dispositivi"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Scegli un\'app per aggiungere controlli"</string> <plurals name="controls_number_of_favorites" formatted="false" msgid="1057347832073807380"> <item quantity="other"><xliff:g id="NUMBER_1">%s</xliff:g> controlli aggiunti.</item> @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selezionato"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivi selezionati"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnesso)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnesso)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossibile connettersi. Riprova."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Accoppia nuovo dispositivo"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero build"</string> @@ -1155,7 +1155,7 @@ <string name="person_available" msgid="2318599327472755472">"Disponibile"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema durante la lettura dell\'indicatore di livello della batteria"</string> <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tocca per ulteriori informazioni"</string> - <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nessuna sveglia"</string> + <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nessuna"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensore di impronte digitali"</string> <string name="accessibility_udfps_disabled_button" msgid="4284034245130239384">"Sensore di impronte digitali disattivato"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"effettuare l\'autenticazione"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index ec9d32346493..32be1c12428a 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"קבוצה"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"נבחר מכשיר אחד"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"נבחרו <xliff:g id="COUNT">%1$d</xliff:g> מכשירים"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (מנותק)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(מנותק)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"לא ניתן היה להתחבר. יש לנסות שוב."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"התאמה של מכשיר חדש"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"מספר Build"</string> @@ -1167,7 +1167,7 @@ <string name="person_available" msgid="2318599327472755472">"אונליין"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"בעיה בקריאת מדדי הסוללה"</string> <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"יש להקיש כדי להציג מידע נוסף"</string> - <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"לא הוגדרה התראה"</string> + <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"לא הוגדרה"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"חיישן טביעות אצבע"</string> <string name="accessibility_udfps_disabled_button" msgid="4284034245130239384">"חיישן טביעות האצבע מושבת"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"אימות"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index dc8f668d494e..ee9790f19ef5 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"グループ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"選択したデバイス: 1 台"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"選択したデバイス: <xliff:g id="COUNT">%1$d</xliff:g> 台"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(未接続)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(接続解除済み)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"接続できませんでした。もう一度お試しください。"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"新しいデバイスとのペア設定"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"ビルド番号"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index bcd7af2d7b98..9d34a60ce28d 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ჯგუფი"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"არჩეულია 1 მოწყობილობა"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"არჩეულია <xliff:g id="COUNT">%1$d</xliff:g> მოწყობილობა"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (კავშირი გაწყვეტილია)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(კავშირი გაწყვეტილია)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"დაკავშირება ვერ მოხერხდა. ცადეთ ხელახლა."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ახალი მოწყობილობის დაწყვილება"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"ანაწყობის ნომერი"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 6685dc370943..6f13d09b2169 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 құрылғы таңдалды."</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> құрылғы таңдалды."</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ажыратылған)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ажыратулы)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Қосылмады. Қайта қосылып көріңіз."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Жаңа құрылғымен жұптау"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Құрама нөмірі"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 70d10f99e48e..8a3953280d33 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ក្រុម"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"បានជ្រើសរើសឧបករណ៍ 1"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"បានជ្រើសរើសឧបករណ៍ <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (បានផ្ដាច់)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(បានដាច់)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"មិនអាចភ្ជាប់បានទេ។ សូមព្យាយាមម្ដងទៀត។"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ផ្គូផ្គងឧបករណ៍ថ្មី"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"លេខកំណែបង្កើត"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 3a1e6a6739a1..7d0661c30a2b 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ಗುಂಪು"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ಸಾಧನವನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ಸಾಧನಗಳನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ಡಿಸ್ಕನೆಕ್ಟ್ ಆಗಿದೆ)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಿ"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"ಬಿಲ್ಡ್ ಸಂಖ್ಯೆ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 4b705fbce07f..bd775daef1e4 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"그룹"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"기기 1대 선택됨"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"기기 <xliff:g id="COUNT">%1$d</xliff:g>대 선택됨"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(연결 끊김)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(연결 끊김)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"연결할 수 없습니다. 다시 시도하세요."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"새 기기와 페어링"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"빌드 번호"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 58114d88f015..272ea77ec5f9 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 түзмөк тандалды"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> түзмөк тандалды"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ажыратылды)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ажыратылды)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Байланышпай койду. Кайталоо."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Жаңы түзмөктү жупташтыруу"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Курама номери"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 9ff47acbbcdc..820dab60f6d5 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ກຸ່ມ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"ເລືອກ 1 ອຸປະກອນແລ້ວ"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"ເລືອກ <xliff:g id="COUNT">%1$d</xliff:g> ອຸປະກອນແລ້ວ"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ຕັດການເຊື່ອມຕໍ່ແລ້ວ)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ຕັດການເຊື່ອມຕໍ່ແລ້ວ)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້. ລອງໃໝ່."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ຈັບຄູ່ອຸປະກອນໃໝ່"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"ໝາຍເລກສ້າງ"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 0161aafb8952..6a4ecfe06870 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupė"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Pasirinktas 1 įrenginys"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Pasirinkta įrenginių: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"„<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“ (atjungta)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(atjungta)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nepavyko prijungti. Bandykite dar kartą."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Naujo įrenginio susiejimas"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijos numeris"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 2ff48e34a0de..0ec38d3c4f81 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -1117,7 +1117,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Atlasīta viena ierīce"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Atlasītas vairākas ierīces (kopā <xliff:g id="COUNT">%1$d</xliff:g>)"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (savienojums pārtraukts)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(savienojums pārtraukts)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nevarēja izveidot savienojumu. Mēģiniet vēlreiz."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Savienošana pārī ar jaunu ierīci"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijas numurs"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 6c495a719af2..e0a971e86046 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Избран е 1 уред"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Избрани се <xliff:g id="COUNT">%1$d</xliff:g> уреди"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (исклучен)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(врската е прекината)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не може да се поврзе. Обидете се повторно."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Спарете нов уред"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Број на верзија"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index db338ef4e634..6fe5815b1543 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ഗ്രൂപ്പ്"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"ഒരു ഉപകരണം തിരഞ്ഞെടുത്തു"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ഉപകരണങ്ങൾ തിരഞ്ഞെടുത്തു"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (വിച്ഛേദിച്ചു)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(വിച്ഛേദിച്ചു)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"കണക്റ്റ് ചെയ്യാനായില്ല. വീണ്ടും ശ്രമിക്കുക."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"പുതിയ ഉപകരണവുമായി ജോടിയാക്കുക"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"ബിൽഡ് നമ്പർ"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index b34b75b7e2e3..86869533af94 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Бүлэг"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 төхөөрөмж сонгосон"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> төхөөрөмж сонгосон"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (салсан)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(салсан)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Холбогдож чадсангүй. Дахин оролдоно уу."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Шинэ төхөөрөмж хослуулах"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Хийцийн дугаар"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 674290d8e7ec..978217ba2d99 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"गट"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिव्हाइस निवडले"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> डिव्हाइस निवडली आहेत"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिस्कनेक्ट केले)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(डिस्कनेक्ट केलेले)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट करू शकलो नाही. पुन्हा प्रयत्न करा."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नवीन डिव्हाइससोबत पेअर करा"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index f3490407de17..35909d8e75f9 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Kumpulan"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 peranti dipilih"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> peranti dipilih"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (diputuskan sambungan)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(diputuskan sambungan)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tidak boleh menyambung. Cuba lagi."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Gandingkan peranti baharu"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nombor binaan"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index b434294ac1d9..ec665e920d43 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -512,7 +512,7 @@ <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ရိုက်ကူးဖမ်းယူခြင်း (သို့) ကာစ်လုပ်ခြင်း စတင်မလား။"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> နှင့် ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string> <string name="media_projection_remember_text" msgid="6896767327140422951">"နောက်ထပ် မပြပါနှင့်"</string> - <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးထုတ်ပစ်ရန်"</string> + <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string> <string name="notification_section_header_incoming" msgid="850925217908095197">"အသစ်"</string> @@ -1111,7 +1111,8 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"အုပ်စု"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"စက်ပစ္စည်း ၁ ခုကို ရွေးချယ်ထားသည်"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"စက်ပစ္စည်း <xliff:g id="COUNT">%1$d</xliff:g> ခုကို ရွေးချယ်ထားသည်"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ချိတ်ဆက်မထားပါ)"</string> + <!-- no translation found for media_output_dialog_disconnected (7090512852817111185) --> + <skip /> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ချိတ်ဆက်၍ မရပါ။ ထပ်စမ်းကြည့်ပါ။"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"စက်အသစ် တွဲချိတ်ရန်"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"တည်ဆောက်မှုနံပါတ်"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index c64eb1c7d3ea..0c911203bec3 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet er valgt"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> enheter er valgt"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (frakoblet)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(frakoblet)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kunne ikke koble til. Prøv på nytt."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Koble til en ny enhet"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Delversjonsnummer"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index fad92262c772..c9a97763acc4 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"समूह"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"१ यन्त्र चयन गरियो"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> वटा यन्त्र चयन गरिए"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिस्कनेक्ट गरिएको)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(डिस्कनेक्ट गरिएको छ)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नयाँ डिभाइस कनेक्ट गर्नुहोस्"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नम्बर"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 2dd2bb7fbd21..a28a10ca461d 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Eén apparaat geselecteerd"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> apparaten geselecteerd"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (verbinding verbroken)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(verbinding verbroken)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kan geen verbinding maken. Probeer het nog eens."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Nieuw apparaat koppelen"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build-nummer"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index ee96b319be71..fce44d7e1592 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ଗୋଷ୍ଠୀ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g>ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ସଂଯୋଗ କରାଯାଇପାରିଲା ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ନୂଆ ଡିଭାଇସକୁ ପେୟାର୍ କରନ୍ତୁ"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"ବିଲ୍ଡ ନମ୍ୱର"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 80fae3d62206..26f4641049eb 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"ਗਰੁੱਪ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ਡੀਵਾਈਸ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ਡੀਵਾਈਸਾਂ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ਡਿਸਕਨੈਕਟ ਹੋਇਆ)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ਡਿਸਕਨੈਕਟ ਹੈ)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ਨਵਾਂ ਡੀਵਾਈਸ ਜੋੜਾਬੱਧ ਕਰੋ"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"ਬਿਲਡ ਨੰਬਰ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 7f8f47b187fa..01debf4b52d4 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Wybrano 1 urządzenie"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Wybrane urządzenia: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (rozłączono)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(odłączono)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nie udało się połączyć. Spróbuj ponownie."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sparuj nowe urządzenie"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numer kompilacji"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 41e5a77c5f85..37592ac524bb 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(sem conexão)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível conectar. Tente novamente."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parear novo dispositivo"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index fa10607a4a23..8c044dfe3da9 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desligado)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desligado)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível ligar. Tente novamente."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sincronize o novo dispositivo"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da compilação"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 41e5a77c5f85..37592ac524bb 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(sem conexão)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível conectar. Tente novamente."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parear novo dispositivo"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 0a7ab9040d53..a78167f6da8c 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -1117,7 +1117,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"S-a selectat un dispozitiv"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"S-au selectat <xliff:g id="COUNT">%1$d</xliff:g> dispozitive"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (s-a deconectat)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(deconectat)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nu s-a putut conecta. Reîncercați."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Asociați un nou dispozitiv"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numărul versiunii"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 77677613d7f2..6abfc8707ac6 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Группа"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрано 1 устройство"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Выбрано устройств: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (отключено)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(нет подключения)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не удалось подключиться. Повторите попытку."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Подключить новое устройство"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер сборки"</string> @@ -1167,7 +1167,7 @@ <string name="person_available" msgid="2318599327472755472">"Онлайн"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Не удалось узнать уровень заряда батареи"</string> <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Нажмите, чтобы узнать больше."</string> - <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Будильников нет"</string> + <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Нет будильников"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер отпечатков пальцев"</string> <string name="accessibility_udfps_disabled_button" msgid="4284034245130239384">"Сканер отпечатков пальцев отключен"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"выполнить аутентификацию"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index a42544e908bb..21892c535810 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"සමූහය"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"උපාංග 1ක් තෝරන ලදී"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"උපාංග <xliff:g id="COUNT">%1$d</xliff:g>ක් තෝරන ලදී"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (විසන්ධි විය)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(විසන්ධි විය)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"සම්බන්ධ වීමට නොහැකි විය. නැවත උත්සාහ කරන්න."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"නව උපාංගය යුගල කරන්න"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"නිමැවුම් අංකය"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 8bb7b4a56637..2f2fb378f437 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 vybrané zariadenie"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Počet vybraných zariadení: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (odpojené)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(odpojené)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nepodarilo sa pripojiť. Skúste to znova."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Spárovať nové zariadenie"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo zostavy"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 1e5c20ea9593..8427534c11bb 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izbrana je ena naprava"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Izbranih je toliko naprav: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (povezava prekinjena)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(povezava je prekinjena)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezave ni bilo mogoče vzpostaviti. Poskusite znova."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Seznanitev nove naprave"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Delovna različica"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index a8b0db18cb71..1acc6e83ebaa 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupi"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 pajisje e zgjedhur"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> pajisje të zgjedhura"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (e shkëputur)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(shkëputur)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nuk mund të lidhej. Provo sërish."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Çifto pajisjen e re"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numri i ndërtimit"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index c2d665450caf..8c96a5154283 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -1117,7 +1117,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Изабран је 1 уређај"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Изабраних уређаја: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (веза је прекинута)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(веза је прекинута)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Повезивање није успело. Пробајте поново."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Упари нови уређај"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Број верзије"</string> @@ -1161,7 +1161,7 @@ <string name="person_available" msgid="2318599327472755472">"Доступно"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Проблем са очитавањем мерача батерије"</string> <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Додирните за више информација"</string> - <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Аларм није подешен"</string> + <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Није подешен"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отисак прста"</string> <string name="accessibility_udfps_disabled_button" msgid="4284034245130239384">"Сензор за отисак прста је онемогућен"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"потврдите идентитет"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 7474e2f9310b..8e44ba154764 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -297,8 +297,8 @@ <string name="accessibility_quick_settings_work_mode_changed_on" msgid="1105258550138313384">"Arbetsläget har aktiverats."</string> <string name="accessibility_quick_settings_data_saver_changed_off" msgid="4910847127871603832">"Databesparing har inaktiverats."</string> <string name="accessibility_quick_settings_data_saver_changed_on" msgid="6370606590802623078">"Databesparing har aktiverats."</string> - <string name="accessibility_quick_settings_sensor_privacy_changed_off" msgid="7608378211873807353">"Sensorsekretess har inaktiverats."</string> - <string name="accessibility_quick_settings_sensor_privacy_changed_on" msgid="4267393685085328801">"Sensorsekretess har aktiverats."</string> + <string name="accessibility_quick_settings_sensor_privacy_changed_off" msgid="7608378211873807353">"Sensorintegritet har inaktiverats."</string> + <string name="accessibility_quick_settings_sensor_privacy_changed_on" msgid="4267393685085328801">"Sensorintegritet har aktiverats."</string> <string name="accessibility_brightness" msgid="5391187016177823721">"Skärmens ljusstyrka"</string> <string name="accessibility_ambient_display_charging" msgid="7725523068728128968">"Laddas"</string> <string name="data_usage_disabled_dialog_3g_title" msgid="5716594205739750015">"2G- och 3G-data har pausats"</string> @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet har valts"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> enheter har valts"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (frånkopplad)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(frånkopplad)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Det gick inte att ansluta. Försök igen."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parkoppla en ny enhet"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versionsnummer"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 85fc39551127..f4aefb760f60 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Kikundi"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Umechagua kifaa 1"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Umechagua vifaa <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (hakijaunganishwa)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(imetenganishwa)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Imeshindwa kuunganisha. Jaribu tena."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Oanisha kifaa kipya"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nambari ya muundo"</string> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index ab159e1c4814..040df865bfe5 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -32,4 +32,6 @@ <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. --> <bool name="config_skinnyNotifsInLandscape">false</bool> + + <dimen name="keyguard_indication_margin_bottom">25dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 4e578615f30f..d17ec7379511 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -81,6 +81,7 @@ <dimen name="fab_margin">24dp</dimen> <dimen name="navigation_key_width">128dp</dimen> + <dimen name="navigation_key_padding">25dp</dimen> <!-- Keyboard shortcuts helper --> diff --git a/packages/SystemUI/res/values-sw900dp/dimens.xml b/packages/SystemUI/res/values-sw900dp/dimens.xml index 2cff97692d9d..2758fb439edd 100644 --- a/packages/SystemUI/res/values-sw900dp/dimens.xml +++ b/packages/SystemUI/res/values-sw900dp/dimens.xml @@ -17,15 +17,14 @@ --> <resources> - <dimen name="button_size">80dp</dimen> - <dimen name="navigation_side_padding">@dimen/button_size</dimen> - <dimen name="navigation_key_width">@dimen/button_size</dimen> - <dimen name="navigation_extra_key_width">@dimen/button_size</dimen> - <!-- The maximum width of the navigation bar ripples. --> <dimen name="key_button_ripple_max_width">76dp</dimen> <!-- The padding around the navigation buttons --> <dimen name="navigation_key_padding">0dp</dimen> + <dimen name="button_size">80dp</dimen> + <dimen name="navigation_side_padding">@dimen/button_size</dimen> + <dimen name="navigation_key_width">@dimen/button_size</dimen> + <dimen name="navigation_extra_key_width">@dimen/button_size</dimen> </resources> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 413149ba5391..9b33030acd8f 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"குழு"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 சாதனம் தேர்ந்தெடுக்கப்பட்டுள்ளது"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> சாதனங்கள் தேர்ந்தெடுக்கப்பட்டுள்ளன"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (இணைப்பு துண்டிக்கப்பட்டது)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(துண்டிக்கப்பட்டது)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"இணைக்க முடியவில்லை. மீண்டும் முயலவும்."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"புதிய சாதனத்தை இணைத்தல்"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"பதிப்பு எண்"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 41f45d7a17c8..ca3b09e53aae 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"గ్రూప్"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 పరికరం ఎంచుకోబడింది"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> పరికరాలు ఎంచుకోబడ్డాయి"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (డిస్కనెక్ట్ అయ్యింది)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(డిస్కనెక్ట్ అయ్యింది)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"కనెక్ట్ చేయడం సాధ్యపడలేదు. మళ్లీ ట్రై చేయండి."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"కొత్త పరికరాన్ని పెయిర్ చేయండి"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"బిల్డ్ నంబర్"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 75314ee0a525..340f3103225b 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"กลุ่ม"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"เลือกอุปกรณ์ไว้ 1 รายการ"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"เลือกอุปกรณ์ไว้ <xliff:g id="COUNT">%1$d</xliff:g> รายการ"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ยกเลิกการเชื่อมต่อแล้ว)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ยกเลิกการเชื่อมต่อแล้ว)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"เชื่อมต่อไม่ได้ ลองใหม่"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"จับคู่อุปกรณ์ใหม่"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"หมายเลขบิลด์"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index baefb76e6685..266f77463816 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device ang napili"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> (na) device ang napili"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nakadiskonekta)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(nadiskonekta)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Hindi makakonekta. Subukan ulit."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Magpares ng bagong device"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero ng build"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 905b34c610e0..5abcb4a2550e 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçildi"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> cihaz seçildi"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (bağlı değil)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(bağlantı kesildi)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Bağlanılamadı. Tekrar deneyin."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Yeni cihaz eşle"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Derleme numarası"</string> @@ -1155,7 +1155,7 @@ <string name="person_available" msgid="2318599327472755472">"Müsait"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Pil ölçeriniz okunurken sorun oluştu"</string> <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Daha fazla bilgi için dokunun"</string> - <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm ayarlanmadı"</string> + <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm yok"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Parmak izi sensörü"</string> <string name="accessibility_udfps_disabled_button" msgid="4284034245130239384">"Parmak izi sensörü devre dışı bırakıldı"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"kimlik doğrulaması yapın"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 7f6f91086b0a..fd5c3d449fde 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -1123,7 +1123,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Вибрано 1 пристрій"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Вибрано пристроїв: <xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (відключено)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(від’єднано)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не вдалося підключитися. Повторіть спробу."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Підключити новий пристрій"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер складання"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 4dcfc09889b7..92aad208b4f6 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"گروپ"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 آلہ منتخب کیا گیا"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> آلات منتخب کیے گئے"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (غیر منسلک ہو گیا)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(غیر منسلک ہے)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"منسلک نہیں ہو سکا۔ پھر کوشش کریں۔"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"نئے آلہ کا جوڑا بنائیں"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"بلڈ نمبر"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 5d135423bbe2..a95639a3f423 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Guruh"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ta qurilma tanlandi"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ta qurilma tanlandi"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (uzilgan)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(uzildi)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ulanmadi. Qayta urining."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Yangi qurilmani ulash"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nashr raqami"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 810f31dcf22a..9a63886fb711 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Nhóm"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Đã chọn 1 thiết bị"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Đã chọn <xliff:g id="COUNT">%1$d</xliff:g> thiết bị"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (đã ngắt kết nối)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(đã ngắt kết nối)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Không thể kết nối. Hãy thử lại."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Ghép nối thiết bị mới"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Số bản dựng"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index b7a475d1de63..794b760b64b7 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"群组"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已选择 1 个设备"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已选择 <xliff:g id="COUNT">%1$d</xliff:g> 个设备"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(已断开连接)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(已断开连接)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"无法连接。请重试。"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"与新设备配对"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"版本号"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index df207ad69ce1..d460fb883da1 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已選取 <xliff:g id="COUNT">%1$d</xliff:g> 部裝置"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (已中斷連線)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(已中斷連線)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"無法連線,請再試一次。"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"配對新裝置"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 2fb31207deaf..bbcf0e4ed2b8 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已選取 <xliff:g id="COUNT">%1$d</xliff:g> 部裝置"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (已中斷連線)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(連線中斷)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"無法連線,請再試一次。"</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"配對新裝置"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 96dd7ede6f2e..6c43d3ad374e 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -1111,7 +1111,7 @@ <string name="media_output_dialog_group" msgid="5571251347877452212">"Iqembu"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"idivayisi ekhethiwe e-1"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"amadivayisi akhethiwe angu-<xliff:g id="COUNT">%1$d</xliff:g>"</string> - <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (inqamukile)"</string> + <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(inqamukile)"</string> <string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ayikwazanga ukuxhumeka. Zama futhi."</string> <string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Bhangqa idivayisi entsha"</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Yakha inombolo"</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index db6985d2b61f..e5ea3688ec0d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -56,11 +56,6 @@ <!-- The amount by which the arrow is shifted to avoid the finger--> <dimen name="navigation_edge_finger_offset">48dp</dimen> - <dimen name="floating_rotation_button_diameter">40dp</dimen> - <dimen name="floating_rotation_button_min_margin">20dp</dimen> - <dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen> - <dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen> - <!-- Height of notification icons in the status bar --> <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> @@ -361,8 +356,6 @@ <!-- The width/height of the icon of a navigation button --> <dimen name="navigation_icon_size">32dp</dimen> - <dimen name="navigation_key_padding">0dp</dimen> - <!-- The width of the view containing the menu/ime navigation bar icons --> <dimen name="navigation_extra_key_width">36dp</dimen> @@ -529,6 +522,19 @@ <!-- Size of the icon inside each item in the ringer selector drawer. --> <dimen name="volume_ringer_drawer_icon_size">24dp</dimen> + <!-- The maximum width of the navigation bar ripples. --> + <dimen name="key_button_ripple_max_width">95dp</dimen> + + <dimen name="rounded_corner_content_padding">0dp</dimen> + + <dimen name="navigation_key_padding">0dp</dimen> + + <!-- Floating rotation button --> + <dimen name="floating_rotation_button_diameter">40dp</dimen> + <dimen name="floating_rotation_button_min_margin">20dp</dimen> + <dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen> + <dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen> + <!-- Gravity for the notification panel --> <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top --> @@ -974,9 +980,6 @@ <dimen name="signal_indicator_to_icon_frame_spacing">3dp</dimen> - <!-- The maximum width of the navigation bar ripples. --> - <dimen name="key_button_ripple_max_width">95dp</dimen> - <!-- Inset shadow for FakeShadowDrawable. It is used to avoid gaps between the card and the shadow. --> <dimen name="fake_shadow_inset">1dp</dimen> @@ -1166,7 +1169,6 @@ <!-- The absolute side margins of quick settings --> <dimen name="quick_settings_bottom_margin_media">8dp</dimen> - <dimen name="rounded_corner_content_padding">0dp</dimen> <dimen name="nav_content_padding">0dp</dimen> <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen> <dimen name="nav_quick_scrub_track_thickness">10dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 9bdd572a8eba..3f855c762273 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -419,6 +419,9 @@ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"> <item name="android:buttonCornerRadius">28dp</item> + <item name="android:buttonBarPositiveButtonStyle">@style/Widget.QSDialog.Button</item> + <item name="android:buttonBarNegativeButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item> + <item name="android:buttonBarNeutralButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item> </style> <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" /> @@ -980,12 +983,15 @@ <style name="InternetDialog.Network"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">88dp</item> + <item name="android:layout_marginStart">@dimen/internet_dialog_network_layout_margin</item> <item name="android:layout_marginEnd">@dimen/internet_dialog_network_layout_margin</item> + <item name="android:layout_gravity">center_vertical|start</item> <item name="android:paddingStart">22dp</item> <item name="android:paddingEnd">22dp</item> <item name="android:orientation">horizontal</item> <item name="android:focusable">true</item> <item name="android:clickable">true</item> + <item name="android:background">?android:attr/selectableItemBackground</item> </style> <style name="InternetDialog.NetworkTitle"> diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml new file mode 100644 index 000000000000..fdf73d53e905 --- /dev/null +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<ConstraintSet + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <Constraint + android:id="@+id/clock" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/date" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintHorizontal_chainStyle="packed" + /> + + <Constraint + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/clock" + app:layout_constraintEnd_toStartOf="@id/carrier_group" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="0" + /> + + <Constraint + android:id="@+id/carrier_group" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constrainedWidth="true" + android:layout_gravity="end|center_vertical" + android:layout_marginStart="8dp" + app:layout_constraintStart_toEndOf="@id/date" + app:layout_constraintEnd_toStartOf="@id/statusIcons" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:visibility="invisible" + app:layout_constraintHorizontal_bias="1" + /> + + <Constraint + android:id="@+id/statusIcons" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/carrier_group" + app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + + <Constraint + android:id="@+id/batteryRemainingIcon" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/statusIcons" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + +</ConstraintSet>
\ No newline at end of file diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml new file mode 100644 index 000000000000..72e518eab839 --- /dev/null +++ b/packages/SystemUI/res/xml/qs_header.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<ConstraintSet + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <Constraint + android:id="@+id/clock" + android:layout_width="wrap_content" + android:layout_height="48dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/date" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/carrier_group" + app:layout_constraintHorizontal_bias="0" + /> + + <Constraint + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="48dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@id/clock" + app:layout_constraintHorizontal_bias="0" + /> + + <Constraint + android:id="@+id/carrier_group" + android:layout_width="0dp" + android:layout_height="48dp" + app:layout_constrainedWidth="true" + android:layout_gravity="end|center_vertical" + android:layout_marginStart="8dp" + app:layout_constraintStart_toEndOf="@id/clock" + app:layout_constraintEnd_toStartOf="@id/statusIcons" + app:layout_constraintTop_toTopOf="@id/clock" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + + <Constraint + android:id="@+id/statusIcons" + android:layout_width="wrap_content" + android:layout_height="48dp" + app:layout_constraintStart_toEndOf="@id/carrier_group" + app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" + app:layout_constraintTop_toTopOf="@id/clock" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + + <Constraint + android:id="@+id/batteryRemainingIcon" + android:layout_width="wrap_content" + android:layout_height="48dp" + app:layout_constraintStart_toEndOf="@id/statusIcons" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/clock" + app:layout_constraintBottom_toBottomOf="parent" + /> + +</ConstraintSet>
\ No newline at end of file diff --git a/packages/SystemUI/res/xml/split_header.xml b/packages/SystemUI/res/xml/split_header.xml new file mode 100644 index 000000000000..a3ee1e2fae64 --- /dev/null +++ b/packages/SystemUI/res/xml/split_header.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<ConstraintSet + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <Constraint + android:id="@+id/clock" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/date" + /> + + <Constraint + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/clock" + app:layout_constraintEnd_toStartOf="@id/carrier_group" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="0" + /> + + <Constraint + android:id="@+id/carrier_group" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constrainedWidth="true" + android:layout_gravity="end|center_vertical" + android:layout_marginStart="8dp" + app:layout_constraintStart_toEndOf="@id/date" + app:layout_constraintEnd_toStartOf="@id/statusIcons" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_bias="1" + /> + + <Constraint + android:id="@+id/statusIcons" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/carrier_group" + app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + /> + + <Constraint + android:id="@+id/batteryRemainingIcon" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/statusIcons" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + /> + +</ConstraintSet>
\ No newline at end of file diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 4880b124fcdb..25db478e57b7 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -45,10 +45,40 @@ android_library { ":wm_shell-aidls", ":wm_shell_util-sources", ], - + libs: [ + "SystemUI-flags", + ], static_libs: [ "PluginCoreLib", "androidx.dynamicanimation_dynamicanimation", + "androidx.concurrent_concurrent-futures", + ], + java_version: "1.8", + min_sdk_version: "current", +} + +java_library { + name: "SystemUI-flag-types", + srcs: [ + "src/com/android/systemui/flags/Flag.kt", + ], + static_kotlin_stdlib: false, + java_version: "1.8", + min_sdk_version: "current", +} + +java_library { + name: "SystemUIFlagsLib", + srcs: [ + "src/com/android/systemui/flags/**/*.kt", + ], + static_kotlin_stdlib: false, + libs: [ + "androidx.concurrent_concurrent-futures", + ], + static_libs: [ + "SystemUI-flag-types", + "SystemUI-flags", ], java_version: "1.8", min_sdk_version: "current", diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index 68834bc2aa23..68834bc2aa23 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt new file mode 100644 index 000000000000..cbb942bc3015 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt @@ -0,0 +1,152 @@ +/* + * 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.systemui.flags + +import android.content.Context +import android.content.Intent +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.provider.Settings +import androidx.concurrent.futures.CallbackToFutureAdapter +import com.google.common.util.concurrent.ListenableFuture +import org.json.JSONException +import org.json.JSONObject + +class FlagManager constructor( + private val context: Context, + private val handler: Handler +) : FlagReader { + companion object { + const val RECEIVING_PACKAGE = "com.android.systemui" + const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG" + const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS" + const val FIELD_ID = "id" + const val FIELD_VALUE = "value" + const val FIELD_TYPE = "type" + const val TYPE_BOOLEAN = "boolean" + private const val SETTINGS_PREFIX = "systemui/flags" + } + + private val listeners: MutableSet<FlagReader.Listener> = mutableSetOf() + private val settingsObserver: ContentObserver = SettingsObserver() + + fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> { + val knownFlagMap = Flags.collectFlags() + // Possible todo in the future: query systemui async to actually get the known flag ids. + return CallbackToFutureAdapter.getFuture( + CallbackToFutureAdapter.Resolver { + completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> -> + completer.set(knownFlagMap.values as Collection<Flag<*>>) + "Retrieving Flags" + }) + } + + fun setFlagValue(id: Int, enabled: Boolean) { + val intent = createIntent(id) + intent.putExtra(FIELD_VALUE, enabled) + + context.sendBroadcast(intent) + } + + fun eraseFlag(id: Int) { + val intent = createIntent(id) + + context.sendBroadcast(intent) + } + + override fun isEnabled(id: Int, def: Boolean): Boolean { + return isEnabled(id) ?: def + } + + /** Returns the stored value or null if not set. */ + fun isEnabled(id: Int): Boolean? { + val data: String = Settings.Secure.getString( + context.contentResolver, keyToSettingsPrefix(id)) + if (data.isEmpty()) { + return null + } + val json: JSONObject + try { + json = JSONObject(data) + return if (!assertType(json, TYPE_BOOLEAN)) { + null + } else json.getBoolean(FIELD_VALUE) + } catch (e: JSONException) { + throw InvalidFlagStorageException() + } + } + + override fun addListener(listener: FlagReader.Listener) { + synchronized(listeners) { + val registerNeeded = listeners.isEmpty() + listeners.add(listener) + if (registerNeeded) { + context.contentResolver.registerContentObserver( + Settings.Secure.getUriFor(SETTINGS_PREFIX), true, settingsObserver) + } + } + } + + override fun removeListener(listener: FlagReader.Listener) { + synchronized(listeners) { + val isRegistered = !listeners.isEmpty() + listeners.remove(listener) + if (isRegistered && listeners.isEmpty()) { + context.contentResolver.unregisterContentObserver(settingsObserver) + } + } + } + + private fun createIntent(id: Int): Intent { + val intent = Intent(ACTION_SET_FLAG) + intent.setPackage(RECEIVING_PACKAGE) + intent.putExtra(FIELD_ID, id) + + return intent + } + + fun keyToSettingsPrefix(key: Int): String { + return SETTINGS_PREFIX + "/" + key + } + + private fun assertType(json: JSONObject, type: String): Boolean { + return try { + json.getString(FIELD_TYPE) == TYPE_BOOLEAN + } catch (e: JSONException) { + false + } + } + + inner class SettingsObserver : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (uri == null) { + return + } + val parts = uri.pathSegments + val idStr = parts[parts.size - 1] + try { + val id = idStr.toInt() + listeners.forEach { l -> l.onFlagChanged(id) } + } catch (e: NumberFormatException) { + // no-op + } + } + } +} + +class InvalidFlagStorageException : Exception("Data found but is invalid")
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt index 1ae8c1f2a712..ee6dea5364f4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt @@ -13,28 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package com.android.systemui.flags; - +package com.android.systemui.flags /** * Plugin for loading flag values */ -public interface FlagReader { - /** Returns a boolean value for the given flag. */ - default boolean isEnabled(int id, boolean def) { - return def; +interface FlagReader { + /** Returns a boolean value for the given flag. */ + fun isEnabled(id: Int, def: Boolean): Boolean { + return def } - /** Add a listener to be alerted when any flag changes. */ - default void addListener(Listener listener) {} + /** Add a listener to be alerted when any flag changes. */ + fun addListener(listener: Listener) {} - /** Remove a listener to be alerted when any flag changes. */ - default void removeListener(Listener listener) {} + /** Remove a listener to be alerted when any flag changes. */ + fun removeListener(listener: Listener) {} - /** A simple listener to be alerted when a flag changes. */ - interface Listener { - /** */ - void onFlagChanged(int id); + /** A simple listener to be alerted when a flag changes. */ + fun interface Listener { + /** */ + fun onFlagChanged(id: Int) } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java index 00124ac01cc4..07ad0c8a5120 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java +++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java @@ -33,10 +33,9 @@ import android.view.RenderNodeAnimator; import android.view.View; import android.view.ViewConfiguration; import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; -import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; - +import androidx.annotation.DimenRes; import androidx.annotation.Keep; import java.util.ArrayList; @@ -49,6 +48,8 @@ public class KeyButtonRipple extends Drawable { private static final float GLOW_MAX_ALPHA_DARK = 0.1f; private static final int ANIMATION_DURATION_SCALE = 350; private static final int ANIMATION_DURATION_FADE = 450; + private static final Interpolator ALPHA_OUT_INTERPOLATOR = + new PathInterpolator(0f, 0f, 0.8f, 1f); private Paint mRipplePaint; private CanvasProperty<Float> mLeftProp; @@ -88,8 +89,8 @@ public class KeyButtonRipple extends Drawable { private Type mType = Type.ROUNDED_RECT; - public KeyButtonRipple(Context ctx, View targetView) { - mMaxWidth = ctx.getResources().getDimensionPixelSize(R.dimen.key_button_ripple_max_width); + public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) { + mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource); mTargetView = targetView; } @@ -336,7 +337,7 @@ public class KeyButtonRipple extends Drawable { private void exitSoftware() { ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f); - alphaAnimator.setInterpolator(Interpolators.ALPHA_OUT); + alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR); alphaAnimator.setDuration(ANIMATION_DURATION_FADE); alphaAnimator.addListener(mAnimatorListener); alphaAnimator.start(); @@ -459,7 +460,7 @@ public class KeyButtonRipple extends Drawable { final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp, RenderNodeAnimator.PAINT_ALPHA, 0); opacityAnim.setDuration(ANIMATION_DURATION_FADE); - opacityAnim.setInterpolator(Interpolators.ALPHA_OUT); + opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR); opacityAnim.addListener(mAnimatorListener); opacityAnim.addListener(mExitHwTraceAnimator); opacityAnim.setTarget(mTargetView); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 5b7e500c4630..be15c700818c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -150,5 +150,10 @@ interface ISystemUiProxy { */ oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48; - // Next id = 49 + /** + * Notifies SystemUI to invoke IME Switcher. + */ + void onImeSwitcherPressed() = 49; + + // Next id = 50 } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java new file mode 100644 index 000000000000..323b20e41a5c --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java @@ -0,0 +1,45 @@ +/* + * 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.systemui.shared.recents.model; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * A group task in the recent tasks list. + * TODO: Move this into Launcher + */ +public class GroupTask { + public @NonNull Task task1; + public @Nullable Task task2; + + public GroupTask(@NonNull Task t1, @Nullable Task t2) { + task1 = t1; + task2 = t2; + } + + public GroupTask(@NonNull GroupTask group) { + task1 = new Task(group.task1); + task2 = group.task2 != null + ? new Task(group.task2) + : null; + } + + public boolean containsTask(int taskId) { + return task1.key.id == taskId || (task2 != null && task2.key.id == taskId); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index e9e9b2421d4a..3f2ff742d072 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -38,6 +38,7 @@ import java.util.Objects; /** * A task in the recent tasks list. + * TODO: Move this into Launcher or see if we can remove now */ public class Task { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index 46057952e079..cbf739732361 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -14,12 +14,17 @@ * limitations under the License. */ -package com.android.systemui.navigationbar.gestural; +package com.android.systemui.shared.rotation; +import android.annotation.DimenRes; +import android.annotation.IdRes; +import android.annotation.LayoutRes; +import android.annotation.StringRes; import android.content.Context; import android.content.res.Resources; -import android.graphics.Color; import android.graphics.PixelFormat; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -27,28 +32,25 @@ import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; -import com.android.systemui.R; -import com.android.systemui.navigationbar.RotationButton; -import com.android.systemui.navigationbar.RotationButtonController; -import com.android.systemui.navigationbar.buttons.KeyButtonDrawable; -import com.android.systemui.navigationbar.buttons.KeyButtonView; -import com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculator.Position; +import androidx.core.view.OneShotPreDrawListener; + +import com.android.systemui.shared.R; +import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position; /** * Containing logic for the rotation button on the physical left bottom corner of the screen. */ public class FloatingRotationButton implements RotationButton { - private static final float BACKGROUND_ALPHA = 0.92f; private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300; private final WindowManager mWindowManager; private final ViewGroup mKeyButtonContainer; - private final KeyButtonView mKeyButtonView; + private final FloatingRotationButtonView mKeyButtonView; private final int mContainerSize; - private KeyButtonDrawable mKeyButtonDrawable; + private AnimatedVectorDrawable mAnimatedDrawable; private boolean mIsShowing; private boolean mCanShow = true; private int mDisplayRotation; @@ -62,28 +64,33 @@ public class FloatingRotationButton implements RotationButton { private RotationButtonUpdatesCallback mUpdatesCallback; private Position mPosition; - public FloatingRotationButton(Context context) { + public FloatingRotationButton(Context context, @StringRes int contentDescription, + @LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin, + @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin, + @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter, + @DimenRes int rippleMaxWidth) { mWindowManager = context.getSystemService(WindowManager.class); - mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate( - R.layout.rotate_suggestion, null); - mKeyButtonView = mKeyButtonContainer.findViewById(R.id.rotate_suggestion); + mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null); + mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId); mKeyButtonView.setVisibility(View.VISIBLE); + mKeyButtonView.setContentDescription(context.getString(contentDescription)); + mKeyButtonView.setRipple(rippleMaxWidth); Resources res = context.getResources(); int defaultMargin = Math.max( - res.getDimensionPixelSize(R.dimen.floating_rotation_button_min_margin), - res.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)); + res.getDimensionPixelSize(minMargin), + res.getDimensionPixelSize(roundedContentPadding)); int taskbarMarginLeft = - res.getDimensionPixelSize(R.dimen.floating_rotation_button_taskbar_left_margin); + res.getDimensionPixelSize(taskbarLeftMargin); int taskbarMarginBottom = - res.getDimensionPixelSize(R.dimen.floating_rotation_button_taskbar_bottom_margin); + res.getDimensionPixelSize(taskbarBottomMargin); mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin, taskbarMarginLeft, taskbarMarginBottom); - final int diameter = res.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter); + final int diameter = res.getDimensionPixelSize(buttonDiameter); mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft, taskbarMarginBottom)); } @@ -113,6 +120,10 @@ public class FloatingRotationButton implements RotationButton { mIsShowing = true; int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + + // TODO(b/200103245): add new window type that has z-index above + // TYPE_NAVIGATION_BAR_PANEL as currently it could be below the taskbar which has + // the same window type final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( mContainerSize, mContainerSize, @@ -134,14 +145,18 @@ public class FloatingRotationButton implements RotationButton { updateTranslation(mPosition, /* animate */ false); mWindowManager.addView(mKeyButtonContainer, lp); - if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) { - mKeyButtonDrawable.resetAnimation(); - mKeyButtonDrawable.startAnimation(); + if (mAnimatedDrawable != null) { + mAnimatedDrawable.reset(); + mAnimatedDrawable.start(); } - if (mUpdatesCallback != null) { - mUpdatesCallback.onVisibilityChanged(true); - } + // Notify about visibility only after first traversal so we can properly calculate + // the touch region for the button + OneShotPreDrawListener.add(mKeyButtonView, () -> { + if (mIsShowing && mUpdatesCallback != null) { + mUpdatesCallback.onVisibilityChanged(true); + } + }); return true; } @@ -166,12 +181,10 @@ public class FloatingRotationButton implements RotationButton { @Override public void updateIcon(int lightIconColor, int darkIconColor) { - Color ovalBackgroundColor = Color.valueOf(Color.red(darkIconColor), - Color.green(darkIconColor), Color.blue(darkIconColor), BACKGROUND_ALPHA); - mKeyButtonDrawable = KeyButtonDrawable.create(mRotationButtonController.getContext(), - lightIconColor, darkIconColor, mRotationButtonController.getIconResId(), - false /* shadow */, ovalBackgroundColor); - mKeyButtonView.setImageDrawable(mKeyButtonDrawable); + mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext() + .getDrawable(mRotationButtonController.getIconResId()); + mKeyButtonView.setImageDrawable(mAnimatedDrawable); + mKeyButtonView.setColors(lightIconColor, darkIconColor); } @Override @@ -185,8 +198,8 @@ public class FloatingRotationButton implements RotationButton { } @Override - public KeyButtonDrawable getImageDrawable() { - return mKeyButtonDrawable; + public Drawable getImageDrawable() { + return mAnimatedDrawable; } @Override @@ -202,6 +215,7 @@ public class FloatingRotationButton implements RotationButton { } } + @Override public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) { mIsTaskbarVisible = taskbarVisible; mIsTaskbarStashed = taskbarStashed; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt index 3ce51ad331c5..ec3c073dc68d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculator.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt @@ -1,4 +1,4 @@ -package com.android.systemui.navigationbar.gestural +package com.android.systemui.shared.rotation import android.view.Gravity import android.view.Surface @@ -7,7 +7,7 @@ import android.view.Surface * Calculates gravity and translation that is necessary to display * the button in the correct position based on the current state */ -internal class FloatingRotationButtonPositionCalculator( +class FloatingRotationButtonPositionCalculator( private val defaultMargin: Int, private val taskbarMarginLeft: Int, private val taskbarMarginBottom: Int diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java new file mode 100644 index 000000000000..c5f8fc15b3b7 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java @@ -0,0 +1,86 @@ +/* + * 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.systemui.shared.rotation; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import androidx.annotation.DimenRes; + +import com.android.systemui.navigationbar.buttons.KeyButtonRipple; + +public class FloatingRotationButtonView extends ImageView { + + private static final float BACKGROUND_ALPHA = 0.92f; + + private KeyButtonRipple mRipple; + private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + + public FloatingRotationButtonView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FloatingRotationButtonView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + setClickable(true); + + setWillNotDraw(false); + forceHasOverlappingRendering(false); + } + + public void setRipple(@DimenRes int rippleMaxWidthResource) { + mRipple = new KeyButtonRipple(getContext(), this, rippleMaxWidthResource); + setBackground(mRipple); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + if (visibility != View.VISIBLE) { + jumpDrawablesToCurrentState(); + } + } + + public void setColors(int lightColor, int darkColor) { + getDrawable().setColorFilter(new PorterDuffColorFilter(lightColor, PorterDuff.Mode.SRC_IN)); + + final int ovalBackgroundColor = Color.valueOf(Color.red(darkColor), + Color.green(darkColor), Color.blue(darkColor), BACKGROUND_ALPHA).toArgb(); + + mOvalBgPaint.setColor(ovalBackgroundColor); + mRipple.setType(KeyButtonRipple.Type.OVAL); + } + + public void setDarkIntensity(float darkIntensity) { + mRipple.setDarkIntensity(darkIntensity); + } + + @Override + public void draw(Canvas canvas) { + int d = Math.min(getWidth(), getHeight()); + canvas.drawOval(0, 0, d, d, mOvalBgPaint); + super.draw(canvas); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java new file mode 100644 index 000000000000..89f71ebf3dce --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java @@ -0,0 +1,59 @@ +/* + * 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.systemui.shared.rotation; + +import android.graphics.drawable.Drawable; +import android.view.View; + +/** + * Interface of a rotation button that interacts {@link RotationButtonController}. + * This interface exists because of the two different styles of rotation button in Sysui, + * one in contextual for 3 button nav and a floating rotation button for gestural. + */ +public interface RotationButton { + default void setRotationButtonController(RotationButtonController rotationButtonController) { } + default void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) { } + + default View getCurrentView() { + return null; + } + default boolean show() { return false; } + default boolean hide() { return false; } + default boolean isVisible() { + return false; + } + default void setCanShowRotationButton(boolean canShow) {} + default void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {} + default void updateIcon(int lightIconColor, int darkIconColor) { } + default void setOnClickListener(View.OnClickListener onClickListener) { } + default void setOnHoverListener(View.OnHoverListener onHoverListener) { } + default Drawable getImageDrawable() { + return null; + } + default void setDarkIntensity(float darkIntensity) { } + default boolean acceptRotationProposal() { + return getCurrentView() != null; + } + + /** + * Callback for updates provided by a rotation button + */ + interface RotationButtonUpdatesCallback { + default void onVisibilityChanged(boolean isVisible) {}; + default void onPositionChanged() {}; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index 0f5c03a2a596..2dbd5dee76aa 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright 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. @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.systemui.navigationbar; +package com.android.systemui.shared.rotation; + +import static android.view.Display.DEFAULT_DISPLAY; import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION; @@ -23,48 +25,51 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.SuppressLint; import android.app.StatusBarManager; import android.content.ContentResolver; import android.content.Context; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; -import android.view.IRotationWatcher.Stub; +import android.view.IRotationWatcher; import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.WindowInsetsController; -import android.view.WindowInsetsController.Behavior; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; -import com.android.systemui.navigationbar.RotationButton.RotationButtonUpdatesCallback; -import com.android.systemui.navigationbar.buttons.KeyButtonDrawable; +import com.android.internal.view.RotationPolicy; +import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.recents.utilities.ViewRippler; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; -import com.android.systemui.statusbar.policy.RotationLockController; import java.util.Optional; import java.util.function.Consumer; +import java.util.function.Supplier; -/** Contains logic that deals with showing a rotate suggestion button with animation. */ +/** + * Contains logic that deals with showing a rotate suggestion button with animation. + */ public class RotationButtonController { private static final String TAG = "StatusBar/RotationButtonController"; private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100; private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000; + private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3; @@ -72,6 +77,7 @@ public class RotationButtonController { private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); private final ViewRippler mViewRippler = new ViewRippler(); + private final Supplier<Integer> mWindowRotationProvider; private RotationButton mRotationButton; private boolean mIsRecentsAnimationRunning; @@ -79,17 +85,30 @@ public class RotationButtonController { private int mLastRotationSuggestion; private boolean mPendingRotationSuggestion; private boolean mHoveringRotationSuggestion; - private RotationLockController mRotationLockController; - private AccessibilityManagerWrapper mAccessibilityManagerWrapper; - private TaskStackListenerImpl mTaskStackListener; + private final AccessibilityManager mAccessibilityManager; + private final TaskStackListenerImpl mTaskStackListener; private Consumer<Integer> mRotWatcherListener; + private boolean mListenersRegistered = false; private boolean mIsNavigationBarShowing; - private @Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT; + @SuppressLint("InlinedApi") + private @WindowInsetsController.Behavior + int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT; private boolean mSkipOverrideUserLockPrefsOnce; - private int mLightIconColor; - private int mDarkIconColor; - private int mIconResId = R.drawable.ic_sysbar_rotate_button_ccw_start_90; + private final int mLightIconColor; + private final int mDarkIconColor; + + @DrawableRes + private final int mIconCcwStart0ResId; + @DrawableRes + private final int mIconCcwStart90ResId; + @DrawableRes + private final int mIconCwStart0ResId; + @DrawableRes + private final int mIconCwStart90ResId; + + @DrawableRes + private int mIconResId; private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false /* visible */); @@ -97,19 +116,20 @@ public class RotationButtonController { () -> mPendingRotationSuggestion = false; private Animator mRotateHideAnimator; - private final Stub mRotationWatcher = new Stub() { + + private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() { @Override - public void onRotationChanged(final int rotation) throws RemoteException { + public void onRotationChanged(final int rotation) { // We need this to be scheduled as early as possible to beat the redrawing of // window in response to the orientation change. mMainThreadHandler.postAtFrontOfQueue(() -> { // If the screen rotation changes while locked, potentially update lock to flow with // new screen rotation and hide any showing suggestions. - if (mRotationLockController.isRotationLocked()) { + if (isRotationLocked()) { if (shouldOverrideUserLockPrefs(rotation)) { setRotationLockedAtAngle(rotation); } - setRotateSuggestionButtonState(false /* visible */, true /* hideImmediately */); + setRotateSuggestionButtonState(false /* visible */, true /* forced */); } if (mRotWatcherListener != null) { @@ -121,27 +141,39 @@ public class RotationButtonController { /** * Determines if rotation suggestions disabled2 flag exists in flag + * * @param disable2Flags see if rotation suggestion flag exists in this flag * @return whether flag exists */ - static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) { + public static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) { return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0; } - RotationButtonController(Context context, @ColorInt int lightIconColor, - @ColorInt int darkIconColor) { + public RotationButtonController(Context context, + @ColorInt int lightIconColor, @ColorInt int darkIconColor, + @DrawableRes int iconCcwStart0ResId, + @DrawableRes int iconCcwStart90ResId, + @DrawableRes int iconCwStart0ResId, + @DrawableRes int iconCwStart90ResId, + Supplier<Integer> windowRotationProvider) { + mContext = context; mLightIconColor = lightIconColor; mDarkIconColor = darkIconColor; - mIsNavigationBarShowing = true; - mRotationLockController = Dependency.get(RotationLockController.class); - mAccessibilityManagerWrapper = Dependency.get(AccessibilityManagerWrapper.class); + mIconCcwStart0ResId = iconCcwStart0ResId; + mIconCcwStart90ResId = iconCcwStart90ResId; + mIconCwStart0ResId = iconCwStart0ResId; + mIconCwStart90ResId = iconCwStart90ResId; + mIconResId = mIconCcwStart90ResId; + + mAccessibilityManager = AccessibilityManager.getInstance(context); mTaskStackListener = new TaskStackListenerImpl(); + mWindowRotationProvider = windowRotationProvider; } - void setRotationButton(RotationButton rotationButton, - RotationButtonUpdatesCallback updatesCallback) { + public void setRotationButton(RotationButton rotationButton, + RotationButtonUpdatesCallback updatesCallback) { mRotationButton = rotationButton; mRotationButton.setRotationButtonController(this); mRotationButton.setOnClickListener(this::onRotateSuggestionClick); @@ -149,7 +181,24 @@ public class RotationButtonController { mRotationButton.setUpdatesCallback(updatesCallback); } - void registerListeners() { + public Context getContext() { + return mContext; + } + + public void init() { + registerListeners(); + if (mContext.getDisplay().getDisplayId() != DEFAULT_DISPLAY) { + // Currently there is no accelerometer sensor on non-default display, disable fixed + // rotation for non-default display + onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS); + } + } + + public void onDestroy() { + unregisterListeners(); + } + + public void registerListeners() { if (mListenersRegistered) { return; } @@ -157,18 +206,19 @@ public class RotationButtonController { mListenersRegistered = true; try { WindowManagerGlobal.getWindowManagerService() - .watchRotation(mRotationWatcher, mContext.getDisplay().getDisplayId()); + .watchRotation(mRotationWatcher, DEFAULT_DISPLAY); } catch (IllegalArgumentException e) { mListenersRegistered = false; Log.w(TAG, "RegisterListeners for the display failed"); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + Log.e(TAG, "RegisterListeners caught a RemoteException", e); + return; } TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); } - void unregisterListeners() { + public void unregisterListeners() { if (!mListenersRegistered) { return; } @@ -177,34 +227,31 @@ public class RotationButtonController { try { WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + Log.e(TAG, "UnregisterListeners caught a RemoteException", e); + return; } TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); } - void setRotationCallback(Consumer<Integer> watcher) { + public void setRotationCallback(Consumer<Integer> watcher) { mRotWatcherListener = watcher; } - void setRotationLockedAtAngle(int rotationSuggestion) { - mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion); + public void setRotationLockedAtAngle(int rotationSuggestion) { + RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion); } public boolean isRotationLocked() { - return mRotationLockController.isRotationLocked(); + return RotationPolicy.isRotationLocked(mContext); } - void setRotateSuggestionButtonState(boolean visible) { - setRotateSuggestionButtonState(visible, false /* hideImmediately */); + public void setRotateSuggestionButtonState(boolean visible) { + setRotateSuggestionButtonState(visible, false /* force */); } - /** - * Change the visibility of rotate suggestion button. If {@code hideImmediately} is true, - * it doesn't wait until the completion of the running animation. - */ - void setRotateSuggestionButtonState(final boolean visible, final boolean hideImmediately) { - // At any point the the button can become invisible because an a11y service became active. + void setRotateSuggestionButtonState(final boolean visible, final boolean force) { + // At any point the button can become invisible because an a11y service became active. // Similarly, a call to make the button visible may be rejected because an a11y service is // active. Must account for this. // Rerun a show animation to indicate change but don't rerun a hide animation @@ -213,7 +260,7 @@ public class RotationButtonController { final View view = mRotationButton.getCurrentView(); if (view == null) return; - final KeyButtonDrawable currentDrawable = mRotationButton.getImageDrawable(); + final Drawable currentDrawable = mRotationButton.getImageDrawable(); if (currentDrawable == null) return; // Clear any pending suggestion flag as it has either been nullified or is being shown @@ -232,11 +279,13 @@ public class RotationButtonController { view.setAlpha(1f); // Run the rotate icon's animation if it has one - if (currentDrawable.canAnimate()) { - currentDrawable.resetAnimation(); - currentDrawable.startAnimation(); + if (currentDrawable instanceof AnimatedVectorDrawable) { + ((AnimatedVectorDrawable) currentDrawable).reset(); + ((AnimatedVectorDrawable) currentDrawable).start(); } + // TODO(b/187754252): No idea why this doesn't work. If we remove the "false" + // we see the animation show the pressed state... but it only shows the first time. if (!isRotateSuggestionIntroduced()) mViewRippler.start(view); // Set visibility unless a11y service is active. @@ -244,7 +293,7 @@ public class RotationButtonController { } else { // Hide mViewRippler.stop(); // Prevent any pending ripples, force hide or not - if (hideImmediately) { + if (force) { // If a hide animator is running stop it and make invisible if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { mRotateHideAnimator.pause(); @@ -258,7 +307,7 @@ public class RotationButtonController { ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f); fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS); - fadeOut.setInterpolator(Interpolators.LINEAR); + fadeOut.setInterpolator(LINEAR_INTERPOLATOR); fadeOut.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -271,29 +320,34 @@ public class RotationButtonController { } } - void setRecentsAnimationRunning(boolean running) { + public void setDarkIntensity(float darkIntensity) { + mRotationButton.setDarkIntensity(darkIntensity); + } + + public void setRecentsAnimationRunning(boolean running) { mIsRecentsAnimationRunning = running; updateRotationButtonStateInOverview(); } - void setHomeRotationEnabled(boolean enabled) { + public void setHomeRotationEnabled(boolean enabled) { mHomeRotationEnabled = enabled; updateRotationButtonStateInOverview(); } private void updateRotationButtonStateInOverview() { if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) { - setRotateSuggestionButtonState(false, true /* hideImmediately */ ); + setRotateSuggestionButtonState(false, true /* hideImmediately */); } } - void setDarkIntensity(float darkIntensity) { - mRotationButton.setDarkIntensity(darkIntensity); - } + public void onRotationProposal(int rotation, boolean isValid) { + int windowRotation = mWindowRotationProvider.get(); + + if (!mRotationButton.acceptRotationProposal()) { + return; + } - void onRotationProposal(int rotation, int windowRotation, boolean isValid) { - if (!mRotationButton.acceptRotationProposal() || (!mHomeRotationEnabled - && mIsRecentsAnimationRunning)) { + if (!mHomeRotationEnabled && mIsRecentsAnimationRunning) { return; } @@ -316,13 +370,9 @@ public class RotationButtonController { mLastRotationSuggestion = rotation; // Remember rotation for click final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation); if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) { - mIconResId = rotationCCW - ? R.drawable.ic_sysbar_rotate_button_ccw_start_90 - : R.drawable.ic_sysbar_rotate_button_cw_start_90; + mIconResId = rotationCCW ? mIconCcwStart0ResId : mIconCwStart0ResId; } else { // 90 or 270 - mIconResId = rotationCCW - ? R.drawable.ic_sysbar_rotate_button_ccw_start_0 - : R.drawable.ic_sysbar_rotate_button_ccw_start_0; + mIconResId = rotationCCW ? mIconCcwStart90ResId : mIconCwStart90ResId; } mRotationButton.updateIcon(mLightIconColor, mDarkIconColor); @@ -339,56 +389,66 @@ public class RotationButtonController { } } - void onDisable2FlagChanged(int state2) { + public void onDisable2FlagChanged(int state2) { final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2); if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled(); } - void onNavigationBarWindowVisibilityChange(boolean showing) { - if (mIsNavigationBarShowing != showing) { - mIsNavigationBarShowing = showing; - showPendingRotationButtonIfNeeded(); + public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) { + if (DEFAULT_DISPLAY != displayId) { + return; } - } - void onBehaviorChanged(@Behavior int behavior) { if (mBehavior != behavior) { mBehavior = behavior; showPendingRotationButtonIfNeeded(); } } + public void onNavigationBarWindowVisibilityChange(boolean showing) { + if (mIsNavigationBarShowing != showing) { + mIsNavigationBarShowing = showing; + showPendingRotationButtonIfNeeded(); + } + } + + public void onTaskbarStateChange(boolean visible, boolean stashed) { + getRotationButton().onTaskbarStateChanged(visible, stashed); + } + private void showPendingRotationButtonIfNeeded() { if (canShowRotationButton() && mPendingRotationSuggestion) { showAndLogRotationSuggestion(); } } - /** Return true when either the nav bar is visible or it's in visual immersive mode. */ + /** + * Return true when either the task bar is visible or it's in visual immersive mode. + */ + @SuppressLint("InlinedApi") private boolean canShowRotationButton() { return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT; } - public Context getContext() { - return mContext; - } - - RotationButton getRotationButton() { - return mRotationButton; - } - - public @DrawableRes int getIconResId() { + @DrawableRes + public int getIconResId() { return mIconResId; } - public @ColorInt int getLightIconColor() { + @ColorInt + public int getLightIconColor() { return mLightIconColor; } - public @ColorInt int getDarkIconColor() { + @ColorInt + public int getDarkIconColor() { return mDarkIconColor; } + public RotationButton getRotationButton() { + return mRotationButton; + } + private void onRotateSuggestionClick(View v) { mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED); incrementNumAcceptedRotationSuggestionsIfNeeded(); @@ -420,7 +480,7 @@ public class RotationButtonController { * avoid losing original user rotation when display rotation is changed by entering the fixed * orientation overview. */ - void setSkipOverrideUserLockPrefsOnce() { + public void setSkipOverrideUserLockPrefsOnce() { mSkipOverrideUserLockPrefsOnce = true; } @@ -451,7 +511,7 @@ public class RotationButtonController { } private int computeRotationProposalTimeout() { - return mAccessibilityManagerWrapper.getRecommendedTimeoutMillis( + return mAccessibilityManager.getRecommendedTimeoutMillis( mHoveringRotationSuggestion ? 16000 : 5000, AccessibilityManager.FLAG_CONTENT_CONTROLS); } @@ -513,11 +573,15 @@ public class RotationButtonController { ROTATION_SUGGESTION_ACCEPTED(207); private final int mId; + RotationButtonEvent(int id) { mId = id; } - @Override public int getId() { + + @Override + public int getId() { return mId; } } } + diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index d447b485f33b..d182399239cc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -57,6 +57,8 @@ public class QuickStepContract { "extra_shell_starting_window"; // See ISmartspaceTransitionController.aidl public static final String KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER = "smartspace_transition"; + // See IRecentTasks.aidl + public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks"; public static final String NAV_BAR_MODE_2BUTTON_OVERLAY = WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index dcc4ea119376..7729a7532f8f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -26,6 +26,7 @@ import static android.view.WindowManager.TransitionOldType; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import android.annotation.SuppressLint; +import android.app.IApplicationThread; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; @@ -51,10 +52,10 @@ public class RemoteAnimationAdapterCompat { private final RemoteTransitionCompat mRemoteTransition; public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration, - long statusBarTransitionDelay) { + long statusBarTransitionDelay, IApplicationThread appThread) { mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration, statusBarTransitionDelay); - mRemoteTransition = buildRemoteTransition(runner); + mRemoteTransition = buildRemoteTransition(runner, appThread); } RemoteAnimationAdapter getWrapped() { @@ -62,9 +63,10 @@ public class RemoteAnimationAdapterCompat { } /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */ - public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner) { + public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner, + IApplicationThread appThread) { return new RemoteTransitionCompat( - new RemoteTransition(wrapRemoteTransition(runner))); + new RemoteTransition(wrapRemoteTransition(runner), appThread)); } public RemoteTransitionCompat getRemoteTransition() { 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 9ec95a3c992d..99b6aed497cc 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 @@ -30,6 +30,7 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityTaskManager; +import android.app.IApplicationThread; import android.content.ComponentName; import android.graphics.Rect; import android.os.IBinder; @@ -72,7 +73,7 @@ public class RemoteTransitionCompat implements Parcelable { } public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner, - @NonNull Executor executor) { + @NonNull Executor executor, @Nullable IApplicationThread appThread) { IRemoteTransition remote = new IRemoteTransition.Stub() { @Override public void startAnimation(IBinder transition, TransitionInfo info, @@ -103,12 +104,12 @@ public class RemoteTransitionCompat implements Parcelable { finishAdapter)); } }; - mTransition = new RemoteTransition(remote); + mTransition = new RemoteTransition(remote, appThread); } /** Constructor specifically for recents animation */ public RemoteTransitionCompat(RecentsAnimationListener recents, - RecentsAnimationControllerCompat controller) { + RecentsAnimationControllerCompat controller, IApplicationThread appThread) { IRemoteTransition remote = new IRemoteTransition.Stub() { final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap(); IBinder mToken = null; @@ -168,7 +169,7 @@ public class RemoteTransitionCompat implements Parcelable { } } }; - mTransition = new RemoteTransition(remote); + mTransition = new RemoteTransition(remote, appThread); } /** Adds a filter check that restricts this remote transition to home open transitions. */ diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java index 5b6845fcdb4f..9311966f82f2 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java @@ -16,6 +16,11 @@ package com.android.systemui.flags; +import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG; +import static com.android.systemui.flags.FlagManager.FIELD_ID; +import static com.android.systemui.flags.FlagManager.FIELD_VALUE; +import static com.android.systemui.flags.FlagManager.FLAGS_PERMISSION; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -23,11 +28,19 @@ import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.settings.SecureSettings; import org.json.JSONException; import org.json.JSONObject; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -43,29 +56,26 @@ import javax.inject.Inject; * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command. */ @SysUISingleton -public class FeatureFlagManager implements FlagReader, FlagWriter { +public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { private static final String TAG = "SysUIFlags"; - private static final String SYSPROP_PREFIX = "persist.systemui.flag_"; - private static final String FIELD_TYPE = "type"; - private static final String FIELD_ID = "id"; - private static final String FIELD_VALUE = "value"; - private static final String TYPE_BOOLEAN = "boolean"; - private static final String ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"; - private static final String FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"; - private final SystemPropertiesHelper mSystemPropertiesHelper; - + private final FlagManager mFlagManager; + private final SecureSettings mSecureSettings; private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>(); @Inject - public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context) { - mSystemPropertiesHelper = systemPropertiesHelper; - + public FeatureFlagManager(FlagManager flagManager, + SecureSettings secureSettings, Context context, + DumpManager dumpManager) { + mFlagManager = flagManager; + mSecureSettings = secureSettings; IntentFilter filter = new IntentFilter(ACTION_SET_FLAG); context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null); + dumpManager.registerDumpable(TAG, this); } /** Return a {@link BooleanFlag}'s value. */ + @Override public boolean isEnabled(int id, boolean defaultValue) { if (!mBooleanFlagCache.containsKey(id)) { Boolean result = isEnabledInternal(id); @@ -77,25 +87,16 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { /** Returns the stored value or null if not set. */ private Boolean isEnabledInternal(int id) { - String data = mSystemPropertiesHelper.get(keyToSysPropKey(id)); - if (data.isEmpty()) { - return null; - } - JSONObject json; try { - json = new JSONObject(data); - if (!assertType(json, TYPE_BOOLEAN)) { - return null; - } - - return json.getBoolean(FIELD_VALUE); - } catch (JSONException e) { - eraseInternal(id); // Don't restart SystemUI in this case. + return mFlagManager.isEnabled(id); + } catch (Exception e) { + eraseInternal(id); } return null; } /** Set whether a given {@link BooleanFlag} is enabled or not. */ + @Override public void setEnabled(int id, boolean value) { Boolean currentValue = isEnabledInternal(id); if (currentValue != null && currentValue == value) { @@ -104,9 +105,9 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { JSONObject json = new JSONObject(); try { - json.put(FIELD_TYPE, TYPE_BOOLEAN); + json.put(FlagManager.FIELD_TYPE, FlagManager.TYPE_BOOLEAN); json.put(FIELD_VALUE, value); - mSystemPropertiesHelper.set(keyToSysPropKey(id), json.toString()); + mSecureSettings.putString(mFlagManager.keyToSettingsPrefix(id), json.toString()); Log.i(TAG, "Set id " + id + " to " + value); restartSystemUI(); } catch (JSONException e) { @@ -123,13 +124,19 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */ private void eraseInternal(int id) { // We can't actually "erase" things from sysprops, but we can set them to empty! - mSystemPropertiesHelper.set(keyToSysPropKey(id), ""); + mSecureSettings.putString(mFlagManager.keyToSettingsPrefix(id), ""); Log.i(TAG, "Erase id " + id); } - public void addListener(Listener run) {} + @Override + public void addListener(Listener run) { + mFlagManager.addListener(run); + } - public void removeListener(Listener run) {} + @Override + public void removeListener(Listener run) { + mFlagManager.removeListener(run); + } private void restartSystemUI() { Log.i(TAG, "Restarting SystemUI"); @@ -137,18 +144,6 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { System.exit(0); } - private static String keyToSysPropKey(int key) { - return SYSPROP_PREFIX + key; - } - - private static boolean assertType(JSONObject json, String type) { - try { - return json.getString(FIELD_TYPE).equals(TYPE_BOOLEAN); - } catch (JSONException e) { - return false; - } - } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -186,4 +181,17 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { } } }; + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("can override: true"); + ArrayList<String> flagStrings = new ArrayList<>(mBooleanFlagCache.size()); + for (Map.Entry<Integer, Boolean> entry : mBooleanFlagCache.entrySet()) { + flagStrings.add(" sysui_flag_" + entry.getKey() + ": " + entry.getValue()); + } + flagStrings.sort(String.CASE_INSENSITIVE_ORDER); + for (String flagString : flagStrings) { + pw.println(flagString); + } + } } diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt new file mode 100644 index 000000000000..bee4d7d3b411 --- /dev/null +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt @@ -0,0 +1,35 @@ +/* + * 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.systemui.flags + +import android.content.Context +import android.os.Handler +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.settings.SettingsUtilModule +import dagger.Module +import dagger.Provides + +@Module(includes = [ + SettingsUtilModule::class +]) +object FlagsModule { + @JvmStatic + @Provides + fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager { + return FlagManager(context, handler) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java new file mode 100644 index 000000000000..6ff175f589a6 --- /dev/null +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java @@ -0,0 +1,72 @@ +/* + * 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.systemui.flags; + +import android.content.Context; +import android.util.SparseBooleanArray; + +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.settings.SecureSettings; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import javax.inject.Inject; + +/** + * Default implementation of the a Flag manager that returns default values for release builds + * + * There's a version of this file in src-debug which allows overriding, and has documentation about + * how to set flags. + */ +@SysUISingleton +public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { + SparseBooleanArray mAccessedFlags = new SparseBooleanArray(); + @Inject + public FeatureFlagManager( + SecureSettings secureSettings, Context context, DumpManager dumpManager) { + dumpManager.registerDumpable("SysUIFlags", this); + } + + @Override + public void addListener(Listener run) {} + + @Override + public void removeListener(Listener run) {} + + @Override + public boolean isEnabled(int key, boolean defaultValue) { + mAccessedFlags.append(key, defaultValue); + return defaultValue; + } + @Override + public void setEnabled(int key, boolean value) {} + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("can override: false"); + int size = mAccessedFlags.size(); + for (int i = 0; i < size; i++) { + pw.println(" sysui_flag_" + mAccessedFlags.keyAt(i) + + ": " + mAccessedFlags.valueAt(i)); + } + } +} diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt new file mode 100644 index 000000000000..7647135bd7fd --- /dev/null +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt @@ -0,0 +1,22 @@ +/* + * 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.systemui.flags + +import dagger.Module + +@Module +object FlagsModule
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java index db729da9c8bf..fcf1b2c9500a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java @@ -20,11 +20,14 @@ import android.os.Bundle; import android.view.View; import android.view.ViewRootImpl; +import androidx.annotation.Nullable; + import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; /** * Interface to control Keyguard View. It should be implemented by KeyguardViewManagers, which @@ -184,14 +187,10 @@ public interface KeyguardViewController { /** * Registers the StatusBar to which this Keyguard View is mounted. - * @param statusBar - * @param notificationPanelViewController - * @param biometricUnlockController - * @param notificationContainer - * @param bypassController */ void registerStatusBar(StatusBar statusBar, NotificationPanelViewController notificationPanelViewController, + @Nullable PanelExpansionStateManager panelExpansionStateManager, BiometricUnlockController biometricUnlockController, View notificationContainer, KeyguardBypassController bypassController); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index c64f416f9672..e96e924d557f 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -30,7 +30,9 @@ import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dagger.WMComponent; import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider; import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider; +import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.recents.RecentTasks; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -119,7 +121,8 @@ public class SystemUIFactory { .setTransitions(mWMComponent.getTransitions()) .setStartingSurface(mWMComponent.getStartingSurface()) .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) - .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()); + .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) + .setRecentTasks(mWMComponent.getRecentTasks()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -133,10 +136,11 @@ public class SystemUIFactory { .setShellCommandHandler(Optional.ofNullable(null)) .setAppPairs(Optional.ofNullable(null)) .setTaskViewFactory(Optional.ofNullable(null)) - .setTransitions(Transitions.createEmptyForTesting()) + .setTransitions(new ShellTransitions() {}) .setDisplayAreaHelper(Optional.ofNullable(null)) .setStartingSurface(Optional.ofNullable(null)) - .setTaskSurfaceHelper(Optional.ofNullable(null)); + .setTaskSurfaceHelper(Optional.ofNullable(null)) + .setRecentTasks(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (mInitializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt index e2a2d07bdf35..b7398d86c16e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt @@ -22,7 +22,6 @@ import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS import android.hardware.display.DisplayManager import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintSensorPropertiesInternal @@ -182,7 +181,6 @@ class SidefpsController @Inject constructor( @BiometricOverlayConstants.ShowReason private fun Int.isReasonToShow(): Boolean = when (this) { REASON_AUTH_KEYGUARD -> false - REASON_AUTH_SETTINGS -> false else -> true } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java index a2e55c0f76e2..70bc56bd1425 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java @@ -23,12 +23,12 @@ import android.graphics.RectF; import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.util.ViewController; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.Optional; /** * Handles: @@ -43,7 +43,7 @@ import java.util.Optional; abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> extends ViewController<T> implements Dumpable { @NonNull final StatusBarStateController mStatusBarStateController; - @NonNull final Optional<StatusBar> mStatusBarOptional; + @NonNull final PanelExpansionStateManager mPanelExpansionStateManager; @NonNull final DumpManager mDumpManger; boolean mNotificationShadeExpanded; @@ -51,11 +51,11 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> protected UdfpsAnimationViewController( T view, @NonNull StatusBarStateController statusBarStateController, - @NonNull Optional<StatusBar> statusBarOptional, + @NonNull PanelExpansionStateManager panelExpansionStateManager, @NonNull DumpManager dumpManager) { super(view); mStatusBarStateController = statusBarStateController; - mStatusBarOptional = statusBarOptional; + mPanelExpansionStateManager = panelExpansionStateManager; mDumpManger = dumpManager; } @@ -63,17 +63,13 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> @Override protected void onViewAttached() { - mStatusBarOptional.ifPresent( - statusBar -> statusBar.addExpansionChangedListener( - mStatusBarExpansionChangedListener)); + mPanelExpansionStateManager.addListener(mPanelExpansionListener); mDumpManger.registerDumpable(getDumpTag(), this); } @Override protected void onViewDetached() { - mStatusBarOptional.ifPresent( - statusBar -> statusBar.removeExpansionChangedListener( - mStatusBarExpansionChangedListener)); + mPanelExpansionStateManager.removeListener(mPanelExpansionListener); mDumpManger.unregisterDumpable(getDumpTag()); } @@ -182,13 +178,13 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> */ void onTouchOutsideView() { } - private final StatusBar.ExpansionChangedListener mStatusBarExpansionChangedListener = - new StatusBar.ExpansionChangedListener() { - @Override - public void onExpansionChanged(float expansion, boolean expanded) { - mNotificationShadeExpanded = expanded; - mView.onExpansionChanged(expansion, expanded); - updatePauseAuth(); - } - }; + private final PanelExpansionListener mPanelExpansionListener = new PanelExpansionListener() { + @Override + public void onPanelExpansionChanged( + float fraction, boolean expanded, boolean tracking) { + mNotificationShadeExpanded = expanded; + mView.onExpansionChanged(fraction, expanded); + updatePauseAuth(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java index 85955e1b5d56..894b29583cc9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java @@ -20,9 +20,7 @@ import android.annotation.NonNull; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.StatusBar; - -import java.util.Optional; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; /** * Class that coordinates non-HBM animations for biometric prompt. @@ -31,9 +29,9 @@ class UdfpsBpViewController extends UdfpsAnimationViewController<UdfpsBpView> { protected UdfpsBpViewController( @NonNull UdfpsBpView view, @NonNull StatusBarStateController statusBarStateController, - @NonNull Optional<StatusBar> statusBarOptional, + @NonNull PanelExpansionStateManager panelExpansionStateManager, @NonNull DumpManager dumpManager) { - super(view, statusBarStateController, statusBarOptional, dumpManager); + super(view, statusBarStateController, panelExpansionStateManager, dumpManager); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 3a3f22a4fda8..c5270aa3d274 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -69,9 +69,9 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -112,7 +112,7 @@ public class UdfpsController implements DozeReceiver { @NonNull private final LayoutInflater mInflater; private final WindowManager mWindowManager; private final DelayableExecutor mFgExecutor; - @NonNull private final Optional<StatusBar> mStatusBarOptional; + @NonNull private final PanelExpansionStateManager mPanelExpansionStateManager; @NonNull private final StatusBarStateController mStatusBarStateController; @NonNull private final KeyguardStateController mKeyguardStateController; @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; @@ -521,7 +521,7 @@ public class UdfpsController implements DozeReceiver { @NonNull WindowManager windowManager, @NonNull StatusBarStateController statusBarStateController, @Main DelayableExecutor fgExecutor, - @NonNull Optional<StatusBar> statusBarOptional, + @NonNull PanelExpansionStateManager panelExpansionStateManager, @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull DumpManager dumpManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -550,7 +550,7 @@ public class UdfpsController implements DozeReceiver { mFingerprintManager = checkNotNull(fingerprintManager); mWindowManager = windowManager; mFgExecutor = fgExecutor; - mStatusBarOptional = statusBarOptional; + mPanelExpansionStateManager = panelExpansionStateManager; mStatusBarStateController = statusBarStateController; mKeyguardStateController = keyguardStateController; mKeyguardViewManager = statusBarKeyguardViewManager; @@ -805,7 +805,7 @@ public class UdfpsController implements DozeReceiver { enrollView, mServerRequest.mEnrollHelper, mStatusBarStateController, - mStatusBarOptional, + mPanelExpansionStateManager, mDumpManager ); case BiometricOverlayConstants.REASON_AUTH_KEYGUARD: @@ -815,7 +815,7 @@ public class UdfpsController implements DozeReceiver { return new UdfpsKeyguardViewController( keyguardView, mStatusBarStateController, - mStatusBarOptional, + mPanelExpansionStateManager, mKeyguardViewManager, mKeyguardUpdateMonitor, mDumpManager, @@ -833,7 +833,7 @@ public class UdfpsController implements DozeReceiver { return new UdfpsBpViewController( bpView, mStatusBarStateController, - mStatusBarOptional, + mPanelExpansionStateManager, mDumpManager ); case BiometricOverlayConstants.REASON_AUTH_OTHER: @@ -843,7 +843,7 @@ public class UdfpsController implements DozeReceiver { return new UdfpsFpmOtherViewController( authOtherView, mStatusBarStateController, - mStatusBarOptional, + mPanelExpansionStateManager, mDumpManager ); default: diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java index af7c3522dc23..292a904af96e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java @@ -22,9 +22,7 @@ import android.graphics.PointF; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.StatusBar; - -import java.util.Optional; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; /** * Class that coordinates non-HBM animations during enrollment. @@ -55,9 +53,9 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp @NonNull UdfpsEnrollView view, @NonNull UdfpsEnrollHelper enrollHelper, @NonNull StatusBarStateController statusBarStateController, - @NonNull Optional<StatusBar> statusBarOptional, + @NonNull PanelExpansionStateManager panelExpansionStateManager, @NonNull DumpManager dumpManager) { - super(view, statusBarStateController, statusBarOptional, dumpManager); + super(view, statusBarStateController, panelExpansionStateManager, dumpManager); mEnrollProgressBarRadius = getContext().getResources() .getInteger(R.integer.config_udfpsEnrollProgressBar); mEnrollHelper = enrollHelper; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java index dcb5aefc8aa3..619873367ee8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java @@ -20,9 +20,7 @@ import android.annotation.NonNull; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.StatusBar; - -import java.util.Optional; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; /** * Class that coordinates non-HBM animations for non keyguard, enrollment or biometric prompt @@ -34,9 +32,9 @@ class UdfpsFpmOtherViewController extends UdfpsAnimationViewController<UdfpsFpmO protected UdfpsFpmOtherViewController( @NonNull UdfpsFpmOtherView view, @NonNull StatusBarStateController statusBarStateController, - @NonNull Optional<StatusBar> statusBarOptional, + @NonNull PanelExpansionStateManager panelExpansionStateManager, @NonNull DumpManager dumpManager) { - super(view, statusBarStateController, statusBarOptional, dumpManager); + super(view, statusBarStateController, panelExpansionStateManager, dumpManager); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 7a28c9d52260..495366c8f69f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -30,16 +30,16 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBouncer; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.time.SystemClock; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.Optional; /** * Class that coordinates non-HBM animations during keyguard authentication. @@ -77,7 +77,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud protected UdfpsKeyguardViewController( @NonNull UdfpsKeyguardView view, @NonNull StatusBarStateController statusBarStateController, - @NonNull Optional<StatusBar> statusBarOptional, + @NonNull PanelExpansionStateManager panelExpansionStateManager, @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, @NonNull DumpManager dumpManager, @@ -87,7 +87,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull KeyguardStateController keyguardStateController, @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @NonNull UdfpsController udfpsController) { - super(view, statusBarStateController, statusBarOptional, dumpManager); + super(view, statusBarStateController, panelExpansionStateManager, dumpManager); mKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockScreenShadeTransitionController = transitionController; @@ -126,9 +126,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN; mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing(); mConfigurationController.addCallback(mConfigurationListener); - mStatusBarOptional.ifPresent( - statusBar -> statusBar.addExpansionChangedListener( - mStatusBarExpansionChangedListener)); + mPanelExpansionStateManager.addListener(mPanelExpansionListener); updateAlpha(); updatePauseAuth(); @@ -147,9 +145,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor); mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); mConfigurationController.removeCallback(mConfigurationListener); - mStatusBarOptional.ifPresent( - statusBar -> statusBar.removeExpansionChangedListener( - mStatusBarExpansionChangedListener)); + mPanelExpansionStateManager.removeListener(mPanelExpansionListener); if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) { mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null); } @@ -403,14 +399,14 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } }; - private final StatusBar.ExpansionChangedListener mStatusBarExpansionChangedListener = - new StatusBar.ExpansionChangedListener() { - @Override - public void onExpansionChanged(float expansion, boolean expanded) { - mStatusBarExpansion = expansion; - updateAlpha(); - } - }; + private final PanelExpansionListener mPanelExpansionListener = new PanelExpansionListener() { + @Override + public void onPanelExpansionChanged( + float fraction, boolean expanded, boolean tracking) { + mStatusBarExpansion = fraction; + updateAlpha(); + } + }; private final KeyguardStateController.Callback mKeyguardStateControllerCallback = new KeyguardStateController.Callback() { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index a9a4da8d94a2..8993af2174c4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -34,6 +34,7 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; @@ -101,6 +102,9 @@ public interface SysUIComponent { @BindsInstance Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t); + @BindsInstance + Builder setRecentTasks(Optional<RecentTasks> r); + 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 a4e2572836f8..d270064b6919 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -18,7 +18,6 @@ package com.android.systemui.dagger; import android.app.INotificationManager; import android.content.Context; -import android.view.LayoutInflater; import androidx.annotation.Nullable; @@ -27,7 +26,6 @@ import com.android.keyguard.clock.ClockModule; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.BootCompleteCache; import com.android.systemui.BootCompleteCacheImpl; -import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; @@ -42,6 +40,7 @@ import com.android.systemui.flags.FeatureFlagManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagReader; import com.android.systemui.flags.FlagWriter; +import com.android.systemui.flags.FlagsModule; import com.android.systemui.fragments.FragmentService; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.model.SysUiState; @@ -66,7 +65,6 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationRowCom import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -104,6 +102,7 @@ import dagger.Provides; ControlsModule.class, DemoModeModule.class, FalsingModule.class, + FlagsModule.class, LogModule.class, PeopleHubModule.class, PluginModule.class, @@ -212,17 +211,4 @@ public abstract class SystemUIModule { groupManager, entryManager, notifPipeline, sysUiState, featureFlags, dumpManager, sysuiMainExecutor)); } - - @Provides - @SysUISingleton - static StatusBarWindowView providesStatusBarWindowView(LayoutInflater layoutInflater) { - StatusBarWindowView view = - (StatusBarWindowView) layoutInflater.inflate(R.layout.super_status_bar, - /* root= */ null); - if (view == null) { - throw new IllegalStateException( - "R.layout.super_status_bar could not be properly inflated"); - } - return view; - } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 618c26b89196..634349ec7ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -32,6 +32,7 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; @@ -110,4 +111,7 @@ public interface WMComponent { @WMSingleton Optional<TaskSurfaceHelper> getTaskSurfaceHelper(); + + @WMSingleton + Optional<RecentTasks> getRecentTasks(); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java deleted file mode 100644 index 85baed4a221c..000000000000 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java +++ /dev/null @@ -1,38 +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.systemui.flags; - -import com.android.systemui.dagger.SysUISingleton; - -import javax.inject.Inject; - -/** - * Default implementation of the a Flag manager that returns default values for release builds - */ -@SysUISingleton -public class FeatureFlagManager implements FlagReader, FlagWriter { - @Inject - public FeatureFlagManager() {} - public boolean isEnabled(String key, boolean defaultValue) { - return defaultValue; - } - public boolean isEnabled(int key, boolean defaultValue) { - return defaultValue; - } - public void setEnabled(String key, boolean value) {} - public void setEnabled(int key, boolean value) {} -} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java index 947a39a32365..ab083a920a10 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java @@ -18,6 +18,8 @@ package com.android.systemui.flags; import android.content.Context; import android.util.FeatureFlagUtils; +import android.util.Log; +import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; @@ -86,6 +88,22 @@ public class FeatureFlags { } } + public void assertLegacyPipelineEnabled() { + if (isNewNotifPipelineRenderingEnabled()) { + throw new IllegalStateException("Old pipeline code running w/ new pipeline enabled"); + } + } + + public boolean checkLegacyPipelineEnabled() { + if (!isNewNotifPipelineRenderingEnabled()) { + return true; + } + Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled", + new Exception()); + Toast.makeText(mContext, "Old pipeline code running!", Toast.LENGTH_SHORT).show(); + return false; + } + public boolean isNewNotifPipelineEnabled() { return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE); } @@ -165,6 +183,13 @@ public class FeatureFlags { return isEnabled(Flags.NEW_USER_SWITCHER); } + /** + * Use the new single view QS headers + */ + public boolean useCombinedQSHeaders() { + return isEnabled(Flags.COMBINED_QS_HEADERS); + } + /** static method for the system setting */ public static boolean isProviderModelSettingEnabled(Context context) { return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 3761d42ae98c..f09c797a6608 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -77,21 +77,24 @@ public class Flags { public static final BooleanFlag NEW_USER_SWITCHER = new BooleanFlag(500, true); + public static final BooleanFlag COMBINED_QS_HEADERS = + new BooleanFlag(501, false); + /***************************************/ // 600- status bar public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS = - new BooleanFlag(501, false); + new BooleanFlag(601, false); /***************************************/ // 700 - dialer/calls public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP = - new BooleanFlag(600, true); + new BooleanFlag(700, true); public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE = - new BooleanFlag(601, true); + new BooleanFlag(701, true); public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = - new BooleanFlag(602, true); + new BooleanFlag(702, true); // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 01a0f271e5b9..22a69d4012fa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -118,6 +118,7 @@ public class KeyguardService extends Service { private final KeyguardViewMediator mKeyguardViewMediator; private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher; + private final ShellTransitions mShellTransitions; private static int newModeToLegacyMode(int newMode) { switch (newMode) { @@ -231,54 +232,10 @@ public class KeyguardService extends Service { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; + mShellTransitions = shellTransitions; if (shellTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS) { - if (sEnableRemoteKeyguardGoingAwayAnimation) { - Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_GOING_AWAY"); - TransitionFilter f = new TransitionFilter(); - f.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY; - shellTransitions.registerRemote(f, - new RemoteTransition(wrap(mExitAnimationRunner))); - } - if (sEnableRemoteKeyguardOccludeAnimation) { - Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE"); - // Register for occluding - TransitionFilter f = new TransitionFilter(); - f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED; - f.mRequirements = new TransitionFilter.Requirement[]{ - new TransitionFilter.Requirement(), new TransitionFilter.Requirement()}; - // First require at-least one app showing that occludes. - f.mRequirements[0].mMustBeIndependent = false; - f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD; - f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; - // Then require that we aren't closing any occludes (because this would mean a - // regular task->task or activity->activity animation not involving keyguard). - f.mRequirements[1].mNot = true; - f.mRequirements[1].mMustBeIndependent = false; - f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD; - f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; - shellTransitions.registerRemote(f, new RemoteTransition(mOccludeAnimation)); - - // Now register for un-occlude. - f = new TransitionFilter(); - f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED; - f.mRequirements = new TransitionFilter.Requirement[]{ - new TransitionFilter.Requirement(), new TransitionFilter.Requirement()}; - // First require at-least one app going-away (doesn't need occlude flag - // as that is implicit by it having been visible and we don't want to exclude - // cases where we are un-occluding because the app removed its showWhenLocked - // capability at runtime). - f.mRequirements[1].mMustBeIndependent = false; - f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; - f.mRequirements[1].mMustBeTask = true; - // Then require that we aren't opening any occludes (otherwise we'd remain - // occluded). - f.mRequirements[0].mNot = true; - f.mRequirements[0].mMustBeIndependent = false; - f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD; - f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; - shellTransitions.registerRemote(f, new RemoteTransition(mUnoccludeAnimation)); - } + // Nothing here. Initialization for this happens in onCreate. } else { RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); if (sEnableRemoteKeyguardGoingAwayAnimation) { @@ -305,6 +262,58 @@ public class KeyguardService extends Service { @Override public void onCreate() { ((SystemUIApplication) getApplication()).startServicesIfNeeded(); + + if (mShellTransitions == null || !Transitions.ENABLE_SHELL_TRANSITIONS) { + return; + } + if (sEnableRemoteKeyguardGoingAwayAnimation) { + Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_GOING_AWAY"); + TransitionFilter f = new TransitionFilter(); + f.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY; + mShellTransitions.registerRemote(f, + new RemoteTransition(wrap(mExitAnimationRunner), getIApplicationThread())); + } + if (sEnableRemoteKeyguardOccludeAnimation) { + Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE"); + // Register for occluding + TransitionFilter f = new TransitionFilter(); + f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED; + f.mRequirements = new TransitionFilter.Requirement[]{ + new TransitionFilter.Requirement(), new TransitionFilter.Requirement()}; + // First require at-least one app showing that occludes. + f.mRequirements[0].mMustBeIndependent = false; + f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD; + f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + // Then require that we aren't closing any occludes (because this would mean a + // regular task->task or activity->activity animation not involving keyguard). + f.mRequirements[1].mNot = true; + f.mRequirements[1].mMustBeIndependent = false; + f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD; + f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; + mShellTransitions.registerRemote(f, + new RemoteTransition(mOccludeAnimation, getIApplicationThread())); + + // Now register for un-occlude. + f = new TransitionFilter(); + f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED; + f.mRequirements = new TransitionFilter.Requirement[]{ + new TransitionFilter.Requirement(), new TransitionFilter.Requirement()}; + // First require at-least one app going-away (doesn't need occlude flag + // as that is implicit by it having been visible and we don't want to exclude + // cases where we are un-occluding because the app removed its showWhenLocked + // capability at runtime). + f.mRequirements[1].mMustBeIndependent = false; + f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; + f.mRequirements[1].mMustBeTask = true; + // Then require that we aren't opening any occludes (otherwise we'd remain + // occluded). + f.mRequirements[0].mNot = true; + f.mRequirements[0].mMustBeIndependent = false; + f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD; + f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + mShellTransitions.registerRemote(f, + new RemoteTransition(mUnoccludeAnimation, getIApplicationThread())); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 19ee50ac99cd..f438181bc367 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -82,6 +82,8 @@ import android.view.WindowManagerPolicyConstants; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import androidx.annotation.Nullable; + import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.policy.IKeyguardDismissCallback; @@ -117,6 +119,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; @@ -2654,10 +2657,16 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, */ public KeyguardViewController registerStatusBar(StatusBar statusBar, NotificationPanelViewController panelView, + @Nullable PanelExpansionStateManager panelExpansionStateManager, BiometricUnlockController biometricUnlockController, View notificationContainer, KeyguardBypassController bypassController) { - mKeyguardViewControllerLazy.get().registerStatusBar(statusBar, panelView, - biometricUnlockController, notificationContainer, bypassController); + mKeyguardViewControllerLazy.get().registerStatusBar( + statusBar, + panelView, + panelExpansionStateManager, + biometricUnlockController, + notificationContainer, + bypassController); return mKeyguardViewControllerLazy.get(); } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt index c1db8edf4119..9e0038112dd3 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -61,7 +61,7 @@ import java.util.Locale * * @param name The name of this buffer * @param maxLogs The maximum number of messages to keep in memory at any one time, including the - * unused pool. + * unused pool. Must be >= [poolSize]. * @param poolSize The maximum amount that the size of the buffer is allowed to flex in response to * sequential calls to [document] that aren't immediately followed by a matching call to [push]. */ @@ -71,6 +71,13 @@ class LogBuffer( private val poolSize: Int, private val logcatEchoTracker: LogcatEchoTracker ) { + init { + if (maxLogs < poolSize) { + throw IllegalArgumentException("maxLogs must be greater than or equal to poolSize, " + + "but maxLogs=$maxLogs < $poolSize=poolSize") + } + } + private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque() var frozen = false diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 72601e9816f9..46e2274970f7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -112,6 +112,17 @@ public class LogModule { } /** + * Provides a logging buffer for logs related to {@link com.android.systemui.qs.QSFragment}'s + * disable flag adjustments. + */ + @Provides + @SysUISingleton + @QSFragmentDisableLog + public static LogBuffer provideQSFragmentDisableLogBuffer(LogBufferFactory factory) { + return factory.create("QSFragmentDisableFlagsLog", 10); + } + + /** * Provides a logging buffer for logs related to swiping away the status bar while in immersive * mode. See {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}. */ diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java new file mode 100644 index 000000000000..557a254e5c09 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java @@ -0,0 +1,36 @@ +/* + * 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.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** + * A {@link LogBuffer} for disable flag adjustments made in + * {@link com.android.systemui.qs.QSFragment}. + */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface QSFragmentDisableLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt index f32dad632721..042a337322e9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt @@ -106,6 +106,7 @@ class PlayerViewHolder private constructor(itemView: View) { */ @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder { val mediaView = inflater.inflate(R.layout.media_view, parent, false) + mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null) // Because this media view (a TransitionLayout) is used to measure and layout the views // in various states before being attached to its parent, we can't depend on the default // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 7809b5fb83ce..6a1eae75f9a9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -131,6 +131,8 @@ import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; +import com.android.systemui.shared.rotation.RotationButton; +import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -944,7 +946,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, // not valid. Just ignore the rotation in this case. if (!mNavigationBarView.isAttachedToWindow()) return; - final int winRotation = mNavigationBarView.getDisplay().getRotation(); final boolean rotateSuggestionsDisabled = RotationButtonController .hasDisable2RotateSuggestionFlag(mDisabledFlags2); final RotationButtonController rotationButtonController = @@ -953,7 +954,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, if (RotationContextButton.DEBUG_ROTATION) { Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation) - + ", winRotation=" + Surface.rotationToString(winRotation) + ", isValid=" + isValid + ", mNavBarWindowState=" + StatusBarManager.windowStateToString(mNavigationBarWindowState) + ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled @@ -963,7 +963,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, // Respect the disabled flag, no need for action as flag change callback will handle hiding if (rotateSuggestionsDisabled) return; - rotationButtonController.onRotationProposal(rotation, winRotation, isValid); + rotationButtonController.onRotationProposal(rotation, isValid); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index c02cc8dda4c4..680cc5cb5cba 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -67,7 +67,6 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.model.SysUiState; -import com.android.systemui.navigationbar.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.navigationbar.buttons.ButtonDispatcher; import com.android.systemui.navigationbar.buttons.ContextualButton; import com.android.systemui.navigationbar.buttons.ContextualButtonGroup; @@ -76,9 +75,11 @@ import com.android.systemui.navigationbar.buttons.KeyButtonDrawable; import com.android.systemui.navigationbar.buttons.NearestTouchFrame; import com.android.systemui.navigationbar.buttons.RotationContextButton; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; -import com.android.systemui.navigationbar.gestural.FloatingRotationButton; +import com.android.systemui.shared.rotation.FloatingRotationButton; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; +import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; +import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; @@ -322,9 +323,23 @@ public class NavigationBarView extends FrameLayout implements mContextualButtonGroup.addButton(accessibilityButton); mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion, mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0); - mFloatingRotationButton = new FloatingRotationButton(context); - mRotationButtonController = new RotationButtonController(mLightContext, - mLightIconColor, mDarkIconColor); + mFloatingRotationButton = new FloatingRotationButton(mContext, + R.string.accessibility_rotate_button, + R.layout.rotate_suggestion, + R.id.rotate_suggestion, + R.dimen.floating_rotation_button_min_margin, + R.dimen.rounded_corner_content_padding, + R.dimen.floating_rotation_button_taskbar_left_margin, + R.dimen.floating_rotation_button_taskbar_bottom_margin, + R.dimen.floating_rotation_button_diameter, + R.dimen.key_button_ripple_max_width); + mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor, + mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0, + R.drawable.ic_sysbar_rotate_button_ccw_start_90, + R.drawable.ic_sysbar_rotate_button_cw_start_0, + R.drawable.ic_sysbar_rotate_button_cw_start_90, + () -> getDisplay().getRotation()); + updateRotationButton(); mOverviewProxyService = Dependency.get(OverviewProxyService.class); @@ -661,7 +676,7 @@ public class NavigationBarView extends FrameLayout implements } public void setBehavior(@Behavior int behavior) { - mRotationButtonController.onBehaviorChanged(behavior); + mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY, behavior); } @Override @@ -1277,6 +1292,7 @@ public class NavigationBarView extends FrameLayout implements mButtonDispatchers.valueAt(i).onDestroy(); } if (mRotationButtonController != null) { + mFloatingRotationButton.hide(); mRotationButtonController.unregisterListeners(); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java deleted file mode 100644 index 3486c6e75931..000000000000 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2020 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.navigationbar; - -import android.view.View; - -import com.android.systemui.navigationbar.buttons.KeyButtonDrawable; - -/** Interface of a rotation button that interacts {@link RotationButtonController}. */ -public interface RotationButton { - void setRotationButtonController(RotationButtonController rotationButtonController); - void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback); - View getCurrentView(); - boolean show(); - boolean hide(); - boolean isVisible(); - void updateIcon(int lightIconColor, int darkIconColor); - void setOnClickListener(View.OnClickListener onClickListener); - void setOnHoverListener(View.OnHoverListener onHoverListener); - KeyButtonDrawable getImageDrawable(); - void setDarkIntensity(float darkIntensity); - default void setCanShowRotationButton(boolean canShow) {} - default boolean acceptRotationProposal() { - return getCurrentView() != null; - } - - /** - * Callback for updates provided by a rotation button - */ - interface RotationButtonUpdatesCallback { - void onVisibilityChanged(boolean isVisible); - void onPositionChanged(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java index 9ea938325659..debd2ebb51be 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java @@ -168,7 +168,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { setClickable(true); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mRipple = new KeyButtonRipple(context, this); + mRipple = new KeyButtonRipple(context, this, R.dimen.key_button_ripple_max_width); mOverviewProxyService = Dependency.get(OverviewProxyService.class); mInputManager = manager; setBackground(mRipple); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java index ebb67af43a37..ac014b5b4a64 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java @@ -21,8 +21,8 @@ import android.annotation.IdRes; import android.content.Context; import android.view.View; -import com.android.systemui.navigationbar.RotationButton; -import com.android.systemui.navigationbar.RotationButtonController; +import com.android.systemui.shared.rotation.RotationButton; +import com.android.systemui.shared.rotation.RotationButtonController; /** Containing logic for the rotation button in nav bar. */ public class RotationContextButton extends ContextualButton implements RotationButton { diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 4f87cad225c7..98b914672112 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -72,12 +72,6 @@ class FooterActionsController @Inject constructor( private var listening: Boolean = false var expanded = false - set(value) { - if (field != value) { - field = value - updateView() - } - } private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button) private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container) @@ -176,8 +170,7 @@ class FooterActionsController @Inject constructor( } private fun updateView() { - mView.updateEverything(buttonsVisible(), isTunerEnabled(), - multiUserSwitchController.isMultiUserEnabled) + mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled) } override fun onViewDetached() { @@ -191,14 +184,14 @@ class FooterActionsController @Inject constructor( this.listening = listening if (this.listening) { userInfoController.addCallback(onUserInfoChangedListener) + updateView() } else { userInfoController.removeCallback(onUserInfoChangedListener) } } fun disable(state2: Int) { - mView.disable(buttonsVisible(), state2, isTunerEnabled(), - multiUserSwitchController.isMultiUserEnabled) + mView.disable(state2, isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled) } fun setExpansion(headerExpansionFraction: Float) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt index 941e54a55393..f81f7bf73f64 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt @@ -107,7 +107,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( } fun disable( - buttonsVisible: Boolean, state2: Int, isTunerEnabled: Boolean, multiUserEnabled: Boolean @@ -115,16 +114,15 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0 if (disabled == qsDisabled) return qsDisabled = disabled - updateEverything(buttonsVisible, isTunerEnabled, multiUserEnabled) + updateEverything(isTunerEnabled, multiUserEnabled) } fun updateEverything( - buttonsVisible: Boolean, isTunerEnabled: Boolean, multiUserEnabled: Boolean ) { post { - updateVisibilities(buttonsVisible, isTunerEnabled, multiUserEnabled) + updateVisibilities(isTunerEnabled, multiUserEnabled) updateClickabilities() isClickable = false } @@ -137,15 +135,14 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( } private fun updateVisibilities( - buttonsVisible: Boolean, isTunerEnabled: Boolean, multiUserEnabled: Boolean ) { settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE tunerIcon.visibility = if (isTunerEnabled) VISIBLE else INVISIBLE - multiUserSwitch.visibility = if (buttonsVisible && multiUserEnabled) VISIBLE else GONE + multiUserSwitch.visibility = if (multiUserEnabled) VISIBLE else GONE val isDemo = UserManager.isDeviceInDemoMode(context) - settingsButton.visibility = if (isDemo || !buttonsVisible) INVISIBLE else VISIBLE + settingsButton.visibility = if (isDemo) INVISIBLE else VISIBLE } fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 7e5ff8a95562..1784f73e1f53 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -112,6 +112,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + // Pass configuration change to non-attached pages as well. Some config changes will cause + // QS to recreate itself (as determined in FragmentHostManager), but in order to minimize + // those, make sure that all get passed to all pages. + int numPages = mPages.size(); + for (int i = 0; i < numPages; i++) { + View page = mPages.get(i); + if (page.getParent() == null) { + page.dispatchConfigurationChanged(newConfig); + } + } if (mLayoutOrientation != newConfig.orientation) { mLayoutOrientation = newConfig.orientation; mDistributeTiles = true; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index eeca239b3caa..dd876b7c7d24 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -15,6 +15,7 @@ package com.android.systemui.qs; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; +import static com.android.systemui.statusbar.DisableFlagsLogger.DisableState; import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; @@ -101,6 +102,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final MediaHost mQsMediaHost; private final MediaHost mQqsMediaHost; private final QSFragmentComponent.Factory mQsComponentFactory; + private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger; private final QSTileHost mHost; private boolean mShowCollapsedOnKeyguard; private boolean mLastKeyguardAndExpanded; @@ -151,6 +153,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, KeyguardBypassController keyguardBypassController, QSFragmentComponent.Factory qsComponentFactory, + QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, FalsingManager falsingManager, DumpManager dumpManager) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mCommandQueue = commandQueue; @@ -158,6 +161,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQsMediaHost = qsMediaHost; mQqsMediaHost = qqsMediaHost; mQsComponentFactory = qsComponentFactory; + mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger; commandQueue.observe(getLifecycle(), this); mHost = qsTileHost; mFalsingManager = falsingManager; @@ -363,8 +367,14 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (displayId != getContext().getDisplayId()) { return; } + int state2BeforeAdjustment = state2; state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); + mQsFragmentDisableFlagsLogger.logDisableFlagChange( + /* new= */ new DisableState(state1, state2BeforeAdjustment), + /* newAfterLocalModification= */ new DisableState(state1, state2) + ); + final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; if (disabled == mQsDisabled) return; mQsDisabled = disabled; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt new file mode 100644 index 000000000000..17a815e72583 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt @@ -0,0 +1,48 @@ +package com.android.systemui.qs + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.QSFragmentDisableLog +import com.android.systemui.statusbar.DisableFlagsLogger +import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment +import javax.inject.Inject + +/** A helper class for logging disable flag changes made in [QSFragment]. */ +class QSFragmentDisableFlagsLogger @Inject constructor( + @QSFragmentDisableLog private val buffer: LogBuffer, + private val disableFlagsLogger: DisableFlagsLogger +) { + + /** + * Logs a string representing the new state received by [QSFragment] and any modifications that + * were made to the flags locally. + * + * @param new see [DisableFlagsLogger.getDisableFlagsString] + * @param newAfterLocalModification see [DisableFlagsLogger.getDisableFlagsString] + */ + fun logDisableFlagChange( + new: DisableFlagsLogger.DisableState, + newAfterLocalModification: DisableFlagsLogger.DisableState + ) { + buffer.log( + TAG, + LogLevel.INFO, + { + int1 = new.disable1 + int2 = new.disable2 + long1 = newAfterLocalModification.disable1.toLong() + long2 = newAfterLocalModification.disable2.toLong() + }, + { + disableFlagsLogger.getDisableFlagsString( + old = null, + new = DisableFlagsLogger.DisableState(int1, int2), + newAfterLocalModification = + DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt()) + ) + } + ) + } +} + +private const val TAG = "QSFragmentDisableFlagsLog"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 2665f3ad98b3..71eb4a2e6cbb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -41,7 +41,7 @@ import com.android.internal.widget.RemeasuringLinearLayout; import com.android.systemui.R; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -69,7 +69,7 @@ public class QSPanel extends LinearLayout implements Tunable { @Nullable protected View mBrightnessView; @Nullable - protected BrightnessSlider mToggleSliderController; + protected BrightnessSliderController mToggleSliderController; private final H mHandler = new H(); /** Whether or not the QS media player feature is enabled. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 70892a7047c0..6794d5b0cee4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -41,7 +41,7 @@ import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; -import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; @@ -63,7 +63,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final FalsingManager mFalsingManager; private final CommandQueue mCommandQueue; private final BrightnessController mBrightnessController; - private final BrightnessSlider mBrightnessSlider; + private final BrightnessSliderController mBrightnessSliderController; private final BrightnessMirrorHandler mBrightnessMirrorHandler; private boolean mGridContentVisible = true; @@ -99,8 +99,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { QSTileRevealController.Factory qsTileRevealControllerFactory, DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, - BrightnessSlider.Factory brightnessSliderFactory, FalsingManager falsingManager, - CommandQueue commandQueue) { + BrightnessSliderController.Factory brightnessSliderFactory, + FalsingManager falsingManager, CommandQueue commandQueue) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager); mQsSecurityFooter = qsSecurityFooter; @@ -111,10 +111,10 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mCommandQueue = commandQueue; mQsSecurityFooter.setHostEnvironment(qstileHost); - mBrightnessSlider = brightnessSliderFactory.create(getContext(), mView); - mView.setBrightnessView(mBrightnessSlider.getRootView()); + mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView); + mView.setBrightnessView(mBrightnessSliderController.getRootView()); - mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider); + mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController); mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); } @@ -125,7 +125,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mMediaHost.setShowsOnlyActiveMedia(false); mMediaHost.init(MediaHierarchyManager.LOCATION_QS); mQsCustomizerController.init(); - mBrightnessSlider.init(); + mBrightnessSliderController.init(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt index 6de837008d91..4854600994aa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt @@ -34,7 +34,11 @@ class QSSquishinessController @Inject constructor( * Change the height of all tiles and repositions their siblings. */ private fun updateSquishiness() { - // Start by updating the height of all tiles + // Update tile positions in the layout + val tileLayout = quickQSPanelController.tileLayout as TileLayout + tileLayout.setSquishinessFraction(squishiness) + + // Adjust their heights as well for (tile in qsTileHost.tiles) { val tileView = quickQSPanelController.getTileView(tile) (tileView as? HeightOverrideable)?.let { @@ -42,10 +46,6 @@ class QSSquishinessController @Inject constructor( } } - // Update tile positions in the layout - val tileLayout = quickQSPanelController.tileLayout as TileLayout - tileLayout.setSquishinessFraction(squishiness) - // Calculate how much we should move the footer val tileHeightOffset = tileLayout.height - tileLayout.tilesHeight val footerTopMargin = (qqsFooterActionsView.layoutParams as ViewGroup.MarginLayoutParams) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt index 14374ffe9f89..65889d792769 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs import androidx.annotation.VisibleForTesting import com.android.systemui.settings.brightness.BrightnessController -import com.android.systemui.settings.brightness.BrightnessSlider +import com.android.systemui.settings.brightness.BrightnessSliderController import com.android.systemui.settings.brightness.MirroredBrightnessController import com.android.systemui.statusbar.policy.BrightnessMirrorController import javax.inject.Inject @@ -33,10 +33,11 @@ class QuickQSBrightnessController @VisibleForTesting constructor( @Inject constructor( brightnessControllerFactory: BrightnessController.Factory, - brightnessSliderFactory: BrightnessSlider.Factory, + brightnessSliderControllerFactory: BrightnessSliderController.Factory, quickQSPanel: QuickQSPanel ) : this(brightnessControllerFactory = { - val slider = brightnessSliderFactory.create(quickQSPanel.context, quickQSPanel) + val slider = brightnessSliderControllerFactory.create(quickQSPanel.context, + quickQSPanel) slider.init() quickQSPanel.setBrightnessView(slider.rootView) brightnessControllerFactory.create(slider) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 071e0535e7c2..fbfba1b6d0d1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -102,6 +102,8 @@ public class QuickStatusBarHeader extends FrameLayout { private boolean mHasCenterCutout; private boolean mConfigShowBatteryEstimate; + private boolean mUseCombinedQSHeader; + public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); } @@ -158,7 +160,9 @@ public class QuickStatusBarHeader extends FrameLayout { void onAttach(TintedIconManager iconManager, QSExpansionPathInterpolator qsExpansionPathInterpolator, - List<String> rssiIgnoredSlots) { + List<String> rssiIgnoredSlots, + boolean useCombinedQSHeader) { + mUseCombinedQSHeader = useCombinedQSHeader; mTintedIconManager = iconManager; mRssiIgnoredSlots = rssiIgnoredSlots; int fillColor = Utils.getColorAttrDefaultColor(getContext(), @@ -233,8 +237,11 @@ public class QuickStatusBarHeader extends FrameLayout { // status bar is already displayed out of QS in split shade boolean shouldUseSplitShade = resources.getBoolean(R.bool.config_use_split_notification_shade); - mStatusIconsView.setVisibility(shouldUseSplitShade ? View.GONE : View.VISIBLE); - mDatePrivacyView.setVisibility(shouldUseSplitShade ? View.GONE : View.VISIBLE); + + mStatusIconsView.setVisibility( + shouldUseSplitShade || mUseCombinedQSHeader ? View.GONE : View.VISIBLE); + mDatePrivacyView.setVisibility( + shouldUseSplitShade || mUseCombinedQSHeader ? View.GONE : View.VISIBLE); mConfigShowBatteryEstimate = resources.getBoolean(R.bool.config_showBatteryEstimateQSBH); @@ -273,8 +280,8 @@ public class QuickStatusBarHeader extends FrameLayout { } MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams(); - qqsLP.topMargin = mContext.getResources() - .getDimensionPixelSize(R.dimen.qqs_layout_margin_top); + qqsLP.topMargin = shouldUseSplitShade || !mUseCombinedQSHeader ? mContext.getResources() + .getDimensionPixelSize(R.dimen.qqs_layout_margin_top) : qsOffsetHeight; mHeaderQsPanel.setLayoutParams(qqsLP); updateBatteryMode(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index 38428c53fead..1b3450436c6f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -225,7 +225,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader ); } - mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots); + mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots, + mFeatureFlags.useCombinedQSHeaders()); mDemoModeController.addCallback(mDemoModeReceiver); diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index ee5d5ffb961c..58c05089b062 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -212,7 +212,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { return mMaxCellHeight; } - private void layoutTileRecords(int numRecords) { + private void layoutTileRecords(int numRecords, boolean forLayout) { final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; int row = 0; int column = 0; @@ -232,14 +232,18 @@ public class TileLayout extends ViewGroup implements QSTileLayout { final int left = getColumnStart(isRtl ? mColumns - column - 1 : column); final int right = left + mCellWidth; final int bottom = top + record.tileView.getMeasuredHeight(); - record.tileView.layout(left, top, right, bottom); + if (forLayout) { + record.tileView.layout(left, top, right, bottom); + } else { + record.tileView.setLeftTopRightBottom(left, top, right, bottom); + } mLastTileBottom = bottom; } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - layoutTileRecords(mRecords.size()); + layoutTileRecords(mRecords.size(), true /* forLayout */); } protected int getRowTop(int row) { @@ -280,6 +284,6 @@ public class TileLayout extends ViewGroup implements QSTileLayout { return; } mSquishinessFraction = squishinessFraction; - layoutTileRecords(mRecords.size()); + layoutTileRecords(mRecords.size(), false /* forLayout */); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 69be33261995..2bd5c8f0fc20 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -242,12 +242,12 @@ open class QSTileViewImpl @JvmOverloads constructor( } private fun updateHeight() { - val actualHeight = (if (heightOverride != HeightOverrideable.NO_OVERRIDE) { + val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) { heightOverride } else { measuredHeight - } * squishinessFraction).toInt() - bottom = top + actualHeight + } + bottom = top + (actualHeight * squishinessFraction).toInt() scrollY = (actualHeight - height) / 2 } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 204dd46189d8..f6dbb0b95ecd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -19,6 +19,7 @@ import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; import android.app.AlertDialog; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.net.Network; import android.net.NetworkCapabilities; @@ -80,7 +81,6 @@ public class InternetDialog extends SystemUIDialog implements private final Handler mHandler; private final Executor mBackgroundExecutor; - private final LinearLayoutManager mLayoutManager; @VisibleForTesting protected InternetAdapter mAdapter; @@ -123,6 +123,7 @@ public class InternetDialog extends SystemUIDialog implements private Switch mWiFiToggle; private FrameLayout mDoneLayout; private Drawable mBackgroundOn; + private Drawable mBackgroundOff = null; private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private boolean mCanConfigMobileData; @@ -161,12 +162,6 @@ public class InternetDialog extends SystemUIDialog implements mCanConfigMobileData = canConfigMobileData; mCanConfigWifi = canConfigWifi; - mLayoutManager = new LinearLayoutManager(mContext) { - @Override - public boolean canScrollVertically() { - return false; - } - }; mUiEventLogger = uiEventLogger; mAdapter = new InternetAdapter(mInternetDialogController); if (!aboveStatusBar) { @@ -216,9 +211,17 @@ public class InternetDialog extends SystemUIDialog implements mInternetDialogTitle.setText(getDialogTitleText()); mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); + TypedArray typedArray = mContext.obtainStyledAttributes( + new int[]{android.R.attr.selectableItemBackground}); + try { + mBackgroundOff = typedArray.getDrawable(0 /* index */); + } finally { + typedArray.recycle(); + } + setOnClickListener(); mTurnWifiOnLayout.setBackground(null); - mWifiRecyclerView.setLayoutManager(mLayoutManager); + mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(mContext)); mWifiRecyclerView.setAdapter(mAdapter); } @@ -371,7 +374,8 @@ public class InternetDialog extends SystemUIDialog implements mMobileSummaryText.setTextAppearance(isCarrierNetworkConnected ? R.style.TextAppearance_InternetDialog_Secondary_Active : R.style.TextAppearance_InternetDialog_Secondary); - mMobileNetworkLayout.setBackground(isCarrierNetworkConnected ? mBackgroundOn : null); + mMobileNetworkLayout.setBackground( + isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff); mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); } @@ -469,10 +473,6 @@ public class InternetDialog extends SystemUIDialog implements } private void setProgressBarVisible(boolean visible) { - if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null - && mAdapter.mHolderView.isAttachedToWindow()) { - mIsProgressBarVisible = true; - } mIsProgressBarVisible = visible; mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE); mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 721a6af37e21..fa874b19c2cc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -17,6 +17,7 @@ package com.android.systemui.recents; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; @@ -24,6 +25,7 @@ import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; @@ -68,6 +70,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.accessibility.AccessibilityManager; +import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; @@ -103,6 +106,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; +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.transition.ShellTransitions; @@ -156,6 +160,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private final ShellTransitions mShellTransitions; private final Optional<StartingSurface> mStartingSurface; private final SmartspaceTransitionController mSmartspaceTransitionController; + private final Optional<RecentTasks> mRecentTasks; private Region mActiveNavBarRegion; @@ -237,6 +242,15 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override + public void onImeSwitcherPressed() throws RemoteException { + // TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since + // Launcher/Taskbar isn't display aware. + mContext.getSystemService(InputMethodManager.class) + .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */, + DEFAULT_DISPLAY); + } + + @Override public void setHomeRotationEnabled(boolean enabled) { verifyCallerAndClearCallingIdentityPostMain("setHomeRotationEnabled", () -> mHandler.post(() -> notifyHomeRotationEnabled(enabled))); @@ -480,6 +494,9 @@ public class OverviewProxyService extends CurrentUserTracker implements params.putBinder( KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER, mSmartspaceTransitionController.createExternalInterface().asBinder()); + mRecentTasks.ifPresent(recentTasks -> params.putBinder( + KEY_EXTRA_RECENT_TASKS, + recentTasks.createExternalInterface().asBinder())); try { mOverviewProxy.onInitialize(params); @@ -530,17 +547,18 @@ public class OverviewProxyService extends CurrentUserTracker implements @Inject public OverviewProxyService(Context context, CommandQueue commandQueue, Lazy<NavigationBarController> navBarControllerLazy, + Lazy<Optional<StatusBar>> statusBarOptionalLazy, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, Optional<Pip> pipOptional, Optional<LegacySplitScreen> legacySplitScreenOptional, Optional<SplitScreen> splitScreenOptional, - Lazy<Optional<StatusBar>> statusBarOptionalLazy, Optional<OneHanded> oneHandedOptional, + Optional<RecentTasks> recentTasks, + Optional<StartingSurface> startingSurface, BroadcastDispatcher broadcastDispatcher, ShellTransitions shellTransitions, ScreenLifecycle screenLifecycle, - Optional<StartingSurface> startingSurface, SmartspaceTransitionController smartspaceTransitionController, DumpManager dumpManager) { super(broadcastDispatcher); @@ -562,6 +580,7 @@ public class OverviewProxyService extends CurrentUserTracker implements mSysUiState.addCallback(this::notifySystemUiStateFlags); mOneHandedOptional = oneHandedOptional; mShellTransitions = shellTransitions; + mRecentTasks = recentTasks; // Assumes device always starts with back button until launcher tells it that it does not mNavBarButtonAlpha = 1.0f; diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index acc6ee130539..d7d1de00c82d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -51,8 +51,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.BrightnessMirrorController; -import java.util.ArrayList; - import javax.inject.Inject; public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController { @@ -92,13 +90,9 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig @Override public void onDisplayChanged(int displayId) { mBackgroundHandler.post(mUpdateSliderRunnable); - notifyCallbacks(); } }; - private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks = - new ArrayList<BrightnessStateChangeCallback>(); - private volatile boolean mAutomatic; // Brightness adjusted automatically using ambient light. private volatile boolean mIsVrModeEnabled; private boolean mListening; @@ -114,11 +108,6 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mControl.setMirrorControllerAndMirror(controller); } - public interface BrightnessStateChangeCallback { - /** Indicates that some of the brightness settings have changed */ - void onBrightnessLevelChanged(); - } - /** ContentObserver to watch brightness */ private class BrightnessObserver extends ContentObserver { @@ -139,7 +128,6 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mBackgroundHandler.post(mUpdateModeRunnable); mBackgroundHandler.post(mUpdateSliderRunnable); } - notifyCallbacks(); } public void startObserving() { @@ -317,14 +305,6 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig Context.VR_SERVICE)); } - public void addStateChangedCallback(BrightnessStateChangeCallback cb) { - mChangeCallbacks.add(cb); - } - - public boolean removeStateChangedCallback(BrightnessStateChangeCallback cb) { - return mChangeCallbacks.remove(cb); - } - public void registerCallbacks() { mBackgroundHandler.post(mStartListeningRunnable); } @@ -375,10 +355,6 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } }); } - - for (BrightnessStateChangeCallback cb : mChangeCallbacks) { - cb.onBrightnessLevelChanged(); - } } public void checkRestrictionAndSetEnabled() { @@ -435,8 +411,12 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } private void animateSliderTo(int target) { - if (!mControlValueInitialized) { + if (!mControlValueInitialized || !mControl.isVisible()) { // Don't animate the first value since its default state isn't meaningful to users. + // We also don't want to animate slider if it's not visible - especially important when + // two sliders are active at the same time in split shade (one in QS and one in QQS), + // as this negatively affects transition between them and they share mirror slider - + // animating it from two different sources causes janky motion mControl.setValue(target); mControlValueInitialized = true; } @@ -455,13 +435,6 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mSliderAnimator.start(); } - private void notifyCallbacks() { - final int size = mChangeCallbacks.size(); - for (int i = 0; i < size; i++) { - mChangeCallbacks.get(i).onBrightnessLevelChanged(); - } - } - /** Factory for creating a {@link BrightnessController}. */ public static class Factory { private final Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 8fc831a7ce4d..c9c1a9b55c3f 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -41,14 +41,14 @@ import javax.inject.Inject; public class BrightnessDialog extends Activity { private BrightnessController mBrightnessController; - private final BrightnessSlider.Factory mToggleSliderFactory; + private final BrightnessSliderController.Factory mToggleSliderFactory; private final BroadcastDispatcher mBroadcastDispatcher; private final Handler mBackgroundHandler; @Inject public BrightnessDialog( BroadcastDispatcher broadcastDispatcher, - BrightnessSlider.Factory factory, + BrightnessSliderController.Factory factory, @Background Handler bgHandler) { mBroadcastDispatcher = broadcastDispatcher; mToggleSliderFactory = factory; @@ -77,7 +77,7 @@ public class BrightnessDialog extends Activity { // The brightness mirror container is INVISIBLE by default. frame.setVisibility(View.VISIBLE); - BrightnessSlider controller = mToggleSliderFactory.create(this, frame); + BrightnessSliderController controller = mToggleSliderFactory.create(this, frame); controller.init(); frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT); diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index b0e320ad1e2f..6c8190af27f7 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -44,7 +44,8 @@ import javax.inject.Inject; * * @see BrightnessMirrorController */ -public class BrightnessSlider extends ViewController<BrightnessSliderView> implements ToggleSlider { +public class BrightnessSliderController extends ViewController<BrightnessSliderView> implements + ToggleSlider { private Listener mListener; private ToggleSlider mMirror; @@ -69,7 +70,7 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple } }; - BrightnessSlider( + BrightnessSliderController( BrightnessSliderView brightnessSliderView, FalsingManager falsingManager) { super(brightnessSliderView); @@ -184,6 +185,15 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple mView.setVisibility(View.VISIBLE); } + @Override + public boolean isVisible() { + // this should be called rarely - once or twice per slider's value change, but not for + // every value change when user slides finger - only the final one. + // If view is not visible this call is quick (around 50 µs) as it sees parent is not visible + // otherwise it's slightly longer (70 µs) because there are more checks to be done + return mView.isVisibleToUser(); + } + private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() { @Override @@ -222,7 +232,7 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple }; /** - * Creates a {@link BrightnessSlider} with its associated view. + * Creates a {@link BrightnessSliderController} with its associated view. */ public static class Factory { @@ -240,11 +250,11 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple * @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated * hierarchy will not be attached */ - public BrightnessSlider create(Context context, @Nullable ViewGroup viewRoot) { + public BrightnessSliderController create(Context context, @Nullable ViewGroup viewRoot) { int layout = getLayout(); BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context) .inflate(layout, viewRoot, false); - return new BrightnessSlider(root, mFalsingManager); + return new BrightnessSliderController(root, mFalsingManager); } /** Get the layout to inflate based on what slider to use */ diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index 15aa2b730adf..0e037ad56257 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -60,6 +60,7 @@ public class BrightnessSliderView extends FrameLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); + setLayerType(LAYER_TYPE_HARDWARE, null); mSlider = requireViewById(R.id.slider); mSlider.setAccessibilityLabel(getContentDescription().toString()); diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java index 5de22d43a21b..648e33b1d228 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java @@ -38,4 +38,5 @@ public interface ToggleSlider { void showView(); void hideView(); + boolean isVisible(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt index cf34db233b06..4272bb14ff3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt @@ -85,24 +85,30 @@ class DisableFlagsLogger constructor( * is no difference. the new-after-modification state also won't be included if there's no * difference from the new state. * - * @param old the disable state that had been previously sent. + * @param old the disable state that had been previously sent. Null if we don't need to log the + * previously sent state. * @param new the new disable state that has just been sent. * @param newAfterLocalModification the new disable states after a class has locally modified * them. Null if the class does not locally modify. */ fun getDisableFlagsString( - old: DisableState, + old: DisableState? = null, new: DisableState, newAfterLocalModification: DisableState? = null ): String { val builder = StringBuilder("Received new disable state. ") - builder.append("Old: ") - builder.append(getFlagsString(old)) - builder.append(" | New: ") - if (old != new) { + + old?.let { + builder.append("Old: ") + builder.append(getFlagsString(old)) + builder.append(" | ") + } + + builder.append("New: ") + if (old != null && old != new) { builder.append(getFlagsStringWithDiff(old, new)) } else { - builder.append(getFlagsString(old)) + builder.append(getFlagsString(new)) } if (newAfterLocalModification != null && new != newAfterLocalModification) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index db7d5c113031..856052e1a4d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -19,8 +19,8 @@ import static android.app.Notification.VISIBILITY_SECRET; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static com.android.systemui.DejankUtils.whitelistIpcs; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import android.app.ActivityManager; import android.app.KeyguardManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 18a3d86589da..1ce7f0350019 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -15,21 +15,16 @@ */ package com.android.systemui.statusbar; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; -import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; -import android.net.Uri; import android.os.Handler; -import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -48,6 +43,9 @@ import android.widget.RemoteViews; import android.widget.RemoteViews.InteractionHandler; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; @@ -55,6 +53,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; 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.statusbar.dagger.StatusBarDependenciesModule; import com.android.systemui.statusbar.notification.NotificationEntryListener; @@ -70,12 +69,10 @@ import com.android.systemui.statusbar.policy.RemoteInputView; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import dagger.Lazy; @@ -93,27 +90,7 @@ public class NotificationRemoteInputManager implements Dumpable { private static final boolean DEBUG = false; private static final String TAG = "NotifRemoteInputManager"; - /** - * How long to wait before auto-dismissing a notification that was kept for remote input, and - * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel - * these given that they technically don't exist anymore. We wait a bit in case the app issues - * an update. - */ - private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; - - /** - * Notifications that are already removed but are kept around because we want to show the - * remote input history. See {@link RemoteInputHistoryExtender} and - * {@link SmartReplyHistoryExtender}. - */ - protected final ArraySet<String> mKeysKeptForRemoteInputHistory = new ArraySet<>(); - - /** - * Notifications that are already removed but are kept around because the remote input is - * actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}. - */ - protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive = - new ArraySet<>(); + private RemoteInputListener mRemoteInputListener; // Dependencies: private final NotificationLockscreenUserManager mLockscreenUserManager; @@ -125,18 +102,17 @@ public class NotificationRemoteInputManager implements Dumpable { private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy; protected final Context mContext; + protected final FeatureFlags mFeatureFlags; private final UserManager mUserManager; private final KeyguardManager mKeyguardManager; + private final RemoteInputNotificationRebuilder mRebuilder; private final StatusBarStateController mStatusBarStateController; private final RemoteInputUriController mRemoteInputUriController; private final NotificationClickNotifier mClickNotifier; protected RemoteInputController mRemoteInputController; - protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback - mNotificationLifetimeFinishedCallback; protected IStatusBarService mBarService; protected Callback mCallback; - protected final ArrayList<NotificationLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); private final List<RemoteInputController.Callback> mControllerCallbacks = new ArrayList<>(); @@ -226,6 +202,7 @@ public class NotificationRemoteInputManager implements Dumpable { ViewGroup actionGroup = (ViewGroup) parent; buttonIndex = actionGroup.indexOfChild(view); } + // TODO(b/204183781): get this from the current pipeline final int count = mEntryManager.getActiveNotificationsCount(); final int rank = entry.getRanking().getRank(); @@ -283,9 +260,11 @@ public class NotificationRemoteInputManager implements Dumpable { */ public NotificationRemoteInputManager( Context context, + FeatureFlags featureFlags, NotificationLockscreenUserManager lockscreenUserManager, SmartReplyController smartReplyController, NotificationEntryManager notificationEntryManager, + RemoteInputNotificationRebuilder rebuilder, Lazy<Optional<StatusBar>> statusBarOptionalLazy, StatusBarStateController statusBarStateController, @Main Handler mainHandler, @@ -294,6 +273,7 @@ public class NotificationRemoteInputManager implements Dumpable { ActionClickLogger logger, DumpManager dumpManager) { mContext = context; + mFeatureFlags = featureFlags; mLockscreenUserManager = lockscreenUserManager; mSmartReplyController = smartReplyController; mEntryManager = notificationEntryManager; @@ -303,7 +283,11 @@ public class NotificationRemoteInputManager implements Dumpable { mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - addLifetimeExtenders(); + mRebuilder = rebuilder; + if (!featureFlags.isNewNotifPipelineRenderingEnabled()) { + mRemoteInputListener = createLegacyRemoteInputLifetimeExtender(mainHandler, + notificationEntryManager, smartReplyController); + } mKeyguardManager = context.getSystemService(KeyguardManager.class); mStatusBarStateController = statusBarStateController; mRemoteInputUriController = remoteInputUriController; @@ -335,10 +319,35 @@ public class NotificationRemoteInputManager implements Dumpable { }); } + /** Add a listener for various remote input events. Works with NEW pipeline only. */ + public void setRemoteInputListener(@NonNull RemoteInputListener remoteInputListener) { + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + if (mRemoteInputListener != null) { + throw new IllegalStateException("mRemoteInputListener is already set"); + } + mRemoteInputListener = remoteInputListener; + if (mRemoteInputController != null) { + mRemoteInputListener.setRemoteInputController(mRemoteInputController); + } + } + } + + @NonNull + @VisibleForTesting + protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender( + Handler mainHandler, + NotificationEntryManager notificationEntryManager, + SmartReplyController smartReplyController) { + return new LegacyRemoteInputLifetimeExtender(); + } + /** Initializes this component with the provided dependencies. */ public void setUpWithCallback(Callback callback, RemoteInputController.Delegate delegate) { mCallback = callback; mRemoteInputController = new RemoteInputController(delegate, mRemoteInputUriController); + if (mRemoteInputListener != null) { + mRemoteInputListener.setRemoteInputController(mRemoteInputController); + } // Register all stored callbacks from before the Controller was initialized. for (RemoteInputController.Callback cb : mControllerCallbacks) { mRemoteInputController.addCallback(cb); @@ -347,19 +356,8 @@ public class NotificationRemoteInputManager implements Dumpable { mRemoteInputController.addCallback(new RemoteInputController.Callback() { @Override public void onRemoteInputSent(NotificationEntry entry) { - if (FORCE_REMOTE_INPUT_HISTORY - && isNotificationKeptForRemoteInputHistory(entry.getKey())) { - mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey()); - } else if (mEntriesKeptForRemoteInputActive.contains(entry)) { - // We're currently holding onto this notification, but from the apps point of - // view it is already canceled, so we'll need to cancel it on the apps behalf - // after sending - unless the app posts an update in the mean time, so wait a - // bit. - mMainHandler.postDelayed(() -> { - if (mEntriesKeptForRemoteInputActive.remove(entry)) { - mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey()); - } - }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); + if (mRemoteInputListener != null) { + mRemoteInputListener.onRemoteInputSent(entry); } try { mBarService.onNotificationDirectReplied(entry.getSbn().getKey()); @@ -381,12 +379,12 @@ public class NotificationRemoteInputManager implements Dumpable { } } }); - mSmartReplyController.setCallback((entry, reply) -> { - StatusBarNotification newSbn = - rebuildNotificationWithRemoteInputInserted(entry, reply, true /* showSpinner */, - null /* mimeType */, null /* uri */); - mEntryManager.updateNotification(newSbn, null /* ranking */); - }); + if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + mSmartReplyController.setCallback((entry, reply) -> { + StatusBarNotification newSbn = mRebuilder.rebuildForSendingSmartReply(entry, reply); + mEntryManager.updateNotification(newSbn, null /* ranking */); + }); + } } public void addControllerCallback(RemoteInputController.Callback callback) { @@ -574,51 +572,47 @@ public class NotificationRemoteInputManager implements Dumpable { if (v == null) { return null; } - return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); - } - - /** - * Adds all the notification lifetime extenders. Each extender represents a reason for the - * NotificationRemoteInputManager to keep a notification lifetime extended. - */ - protected void addLifetimeExtenders() { - mLifetimeExtenders.add(new RemoteInputHistoryExtender()); - mLifetimeExtenders.add(new SmartReplyHistoryExtender()); - mLifetimeExtenders.add(new RemoteInputActiveExtender()); + return v.findViewWithTag(RemoteInputView.VIEW_TAG); } public ArrayList<NotificationLifetimeExtender> getLifetimeExtenders() { - return mLifetimeExtenders; + // OLD pipeline code ONLY; can assume implementation + return ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener).mLifetimeExtenders; } @VisibleForTesting void onPerformRemoveNotification(NotificationEntry entry, final String key) { - if (mKeysKeptForRemoteInputHistory.contains(key)) { - mKeysKeptForRemoteInputHistory.remove(key); - } + // OLD pipeline code ONLY; can assume implementation + ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener) + .mKeysKeptForRemoteInputHistory.remove(key); + cleanUpRemoteInputForUserRemoval(entry); + } + + /** + * Disable remote input on the entry and remove the remote input view. + * This should be called when a user dismisses a notification that won't be lifetime extended. + */ + public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) { if (isRemoteInputActive(entry)) { entry.mRemoteEditImeVisible = false; mRemoteInputController.removeRemoteInput(entry, null); } } + /** Informs the remote input system that the panel has collapsed */ public void onPanelCollapsed() { - for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) { - NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i); - if (mRemoteInputController != null) { - mRemoteInputController.removeRemoteInput(entry, null); - } - if (mNotificationLifetimeFinishedCallback != null) { - mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey()); - } + if (mRemoteInputListener != null) { + mRemoteInputListener.onPanelCollapsed(); } - mEntriesKeptForRemoteInputActive.clear(); } + /** Returns whether the given notification is lifetime extended because of remote input */ public boolean isNotificationKeptForRemoteInputHistory(String key) { - return mKeysKeptForRemoteInputHistory.contains(key); + return mRemoteInputListener != null + && mRemoteInputListener.isNotificationKeptForRemoteInputHistory(key); } + /** Returns whether the notification should be lifetime extended for remote input history */ public boolean shouldKeepForRemoteInputHistory(NotificationEntry entry) { if (!FORCE_REMOTE_INPUT_HISTORY) { return false; @@ -636,16 +630,12 @@ public class NotificationRemoteInputManager implements Dumpable { if (entry == null) { return; } - final String key = entry.getKey(); - if (isNotificationKeptForRemoteInputHistory(key)) { - mMainHandler.postDelayed(() -> { - if (isNotificationKeptForRemoteInputHistory(key)) { - mNotificationLifetimeFinishedCallback.onSafeToRemove(key); - } - }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); + if (mRemoteInputListener != null) { + mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory(entry); } } + /** Returns whether the notification should be lifetime extended for smart reply history */ public boolean shouldKeepForSmartReplyHistory(NotificationEntry entry) { if (!FORCE_REMOTE_INPUT_HISTORY) { return false; @@ -661,64 +651,11 @@ public class NotificationRemoteInputManager implements Dumpable { } } - @VisibleForTesting - StatusBarNotification rebuildNotificationForCanceledSmartReplies( - NotificationEntry entry) { - return rebuildNotificationWithRemoteInputInserted(entry, null /* remoteInputTest */, - false /* showSpinner */, null /* mimeType */, null /* uri */); - } - - @VisibleForTesting - StatusBarNotification rebuildNotificationWithRemoteInputInserted(NotificationEntry entry, - CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) { - StatusBarNotification sbn = entry.getSbn(); - - Notification.Builder b = Notification.Builder - .recoverBuilder(mContext, sbn.getNotification().clone()); - if (remoteInputText != null || uri != null) { - RemoteInputHistoryItem newItem = uri != null - ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText) - : new RemoteInputHistoryItem(remoteInputText); - Parcelable[] oldHistoryItems = sbn.getNotification().extras - .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); - RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null - ? Stream.concat( - Stream.of(newItem), - Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p)) - .toArray(RemoteInputHistoryItem[]::new) - : new RemoteInputHistoryItem[] { newItem }; - b.setRemoteInputHistory(newHistoryItems); - } - b.setShowRemoteInputSpinner(showSpinner); - b.setHideSmartReplies(true); - - Notification newNotification = b.build(); - - // Undo any compatibility view inflation - newNotification.contentView = sbn.getNotification().contentView; - newNotification.bigContentView = sbn.getNotification().bigContentView; - newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; - - return new StatusBarNotification( - sbn.getPackageName(), - sbn.getOpPkg(), - sbn.getId(), - sbn.getTag(), - sbn.getUid(), - sbn.getInitialPid(), - newNotification, - sbn.getUser(), - sbn.getOverrideGroupKey(), - sbn.getPostTime()); - } - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("NotificationRemoteInputManager state:"); - pw.print(" mKeysKeptForRemoteInputHistory: "); - pw.println(mKeysKeptForRemoteInputHistory); - pw.print(" mEntriesKeptForRemoteInputActive: "); - pw.println(mEntriesKeptForRemoteInputActive); + if (mRemoteInputListener instanceof Dumpable) { + ((Dumpable) mRemoteInputListener).dump(fd, pw, args); + } } public void bindRow(ExpandableNotificationRow row) { @@ -734,11 +671,6 @@ public class NotificationRemoteInputManager implements Dumpable { return mInteractionHandler; } - @VisibleForTesting - public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() { - return mEntriesKeptForRemoteInputActive; - } - public boolean isRemoteInputActive() { return mRemoteInputController != null && mRemoteInputController.isRemoteInputActive(); } @@ -758,131 +690,6 @@ public class NotificationRemoteInputManager implements Dumpable { } /** - * NotificationRemoteInputManager has multiple reasons to keep notification lifetime extended - * so we implement multiple NotificationLifetimeExtenders - */ - protected abstract class RemoteInputExtender implements NotificationLifetimeExtender { - @Override - public void setCallback(NotificationSafeToRemoveCallback callback) { - if (mNotificationLifetimeFinishedCallback == null) { - mNotificationLifetimeFinishedCallback = callback; - } - } - } - - /** - * Notification is kept alive as it was cancelled in response to a remote input interaction. - * This allows us to show what you replied and allows you to continue typing into it. - */ - protected class RemoteInputHistoryExtender extends RemoteInputExtender { - @Override - public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { - return shouldKeepForRemoteInputHistory(entry); - } - - @Override - public void setShouldManageLifetime(NotificationEntry entry, - boolean shouldExtend) { - if (shouldExtend) { - CharSequence remoteInputText = entry.remoteInputText; - if (TextUtils.isEmpty(remoteInputText)) { - remoteInputText = entry.remoteInputTextWhenReset; - } - String remoteInputMimeType = entry.remoteInputMimeType; - Uri remoteInputUri = entry.remoteInputUri; - StatusBarNotification newSbn = rebuildNotificationWithRemoteInputInserted(entry, - remoteInputText, false /* showSpinner */, remoteInputMimeType, - remoteInputUri); - entry.onRemoteInputInserted(); - - if (newSbn == null) { - return; - } - - mEntryManager.updateNotification(newSbn, null); - - // Ensure the entry hasn't already been removed. This can happen if there is an - // inflation exception while updating the remote history - if (entry.isRemoved()) { - return; - } - - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Keeping notification around after sending remote input " - + entry.getKey()); - } - - mKeysKeptForRemoteInputHistory.add(entry.getKey()); - } else { - mKeysKeptForRemoteInputHistory.remove(entry.getKey()); - } - } - } - - /** - * Notification is kept alive for smart reply history. Similar to REMOTE_INPUT_HISTORY but with - * {@link SmartReplyController} specific logic - */ - protected class SmartReplyHistoryExtender extends RemoteInputExtender { - @Override - public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { - return shouldKeepForSmartReplyHistory(entry); - } - - @Override - public void setShouldManageLifetime(NotificationEntry entry, - boolean shouldExtend) { - if (shouldExtend) { - StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry); - - if (newSbn == null) { - return; - } - - mEntryManager.updateNotification(newSbn, null); - - if (entry.isRemoved()) { - return; - } - - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Keeping notification around after sending smart reply " - + entry.getKey()); - } - - mKeysKeptForRemoteInputHistory.add(entry.getKey()); - } else { - mKeysKeptForRemoteInputHistory.remove(entry.getKey()); - mSmartReplyController.stopSending(entry); - } - } - } - - /** - * Notification is kept alive because the user is still using the remote input - */ - protected class RemoteInputActiveExtender extends RemoteInputExtender { - @Override - public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { - return isRemoteInputActive(entry); - } - - @Override - public void setShouldManageLifetime(NotificationEntry entry, - boolean shouldExtend) { - if (shouldExtend) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Keeping notification around while remote input active " - + entry.getKey()); - } - mEntriesKeptForRemoteInputActive.add(entry); - } else { - mEntriesKeptForRemoteInputActive.remove(entry); - } - } - } - - /** * Callback for various remote input related events, or for providing information that * NotificationRemoteInputManager needs to know to decide what to do. */ @@ -975,4 +782,256 @@ public class NotificationRemoteInputManager implements Dumpable { */ boolean showBouncerIfNecessary(); } + + /** An interface for listening to remote input events that relate to notification lifetime */ + public interface RemoteInputListener { + /** Called when remote input pending intent has been sent */ + void onRemoteInputSent(@NonNull NotificationEntry entry); + + /** Called when the notification shade becomes fully closed */ + void onPanelCollapsed(); + + /** @return whether lifetime of a notification is being extended by the listener */ + boolean isNotificationKeptForRemoteInputHistory(@NonNull String key); + + /** Called on user interaction to end lifetime extension for history */ + void releaseNotificationIfKeptForRemoteInputHistory(@NonNull NotificationEntry entry); + + /** Called when the RemoteInputController is attached to the manager */ + void setRemoteInputController(@NonNull RemoteInputController remoteInputController); + } + + @VisibleForTesting + protected class LegacyRemoteInputLifetimeExtender implements RemoteInputListener, Dumpable { + + /** + * How long to wait before auto-dismissing a notification that was kept for remote input, + * and has now sent a remote input. We auto-dismiss, because the app may not see a reason to + * cancel these given that they technically don't exist anymore. We wait a bit in case the + * app issues an update. + */ + private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; + + /** + * Notifications that are already removed but are kept around because we want to show the + * remote input history. See {@link RemoteInputHistoryExtender} and + * {@link SmartReplyHistoryExtender}. + */ + protected final ArraySet<String> mKeysKeptForRemoteInputHistory = new ArraySet<>(); + + /** + * Notifications that are already removed but are kept around because the remote input is + * actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}. + */ + protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive = + new ArraySet<>(); + + protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback + mNotificationLifetimeFinishedCallback; + + protected final ArrayList<NotificationLifetimeExtender> mLifetimeExtenders = + new ArrayList<>(); + private RemoteInputController mRemoteInputController; + + LegacyRemoteInputLifetimeExtender() { + addLifetimeExtenders(); + } + + /** + * Adds all the notification lifetime extenders. Each extender represents a reason for the + * NotificationRemoteInputManager to keep a notification lifetime extended. + */ + protected void addLifetimeExtenders() { + mLifetimeExtenders.add(new RemoteInputHistoryExtender()); + mLifetimeExtenders.add(new SmartReplyHistoryExtender()); + mLifetimeExtenders.add(new RemoteInputActiveExtender()); + } + + @Override + public void setRemoteInputController(@NonNull RemoteInputController remoteInputController) { + mRemoteInputController= remoteInputController; + } + + @Override + public void onRemoteInputSent(@NonNull NotificationEntry entry) { + if (FORCE_REMOTE_INPUT_HISTORY + && isNotificationKeptForRemoteInputHistory(entry.getKey())) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey()); + } else if (mEntriesKeptForRemoteInputActive.contains(entry)) { + // We're currently holding onto this notification, but from the apps point of + // view it is already canceled, so we'll need to cancel it on the apps behalf + // after sending - unless the app posts an update in the mean time, so wait a + // bit. + mMainHandler.postDelayed(() -> { + if (mEntriesKeptForRemoteInputActive.remove(entry)) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey()); + } + }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); + } + } + + @Override + public void onPanelCollapsed() { + for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) { + NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i); + if (mRemoteInputController != null) { + mRemoteInputController.removeRemoteInput(entry, null); + } + if (mNotificationLifetimeFinishedCallback != null) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey()); + } + } + mEntriesKeptForRemoteInputActive.clear(); + } + + @Override + public boolean isNotificationKeptForRemoteInputHistory(@NonNull String key) { + return mKeysKeptForRemoteInputHistory.contains(key); + } + + @Override + public void releaseNotificationIfKeptForRemoteInputHistory( + @NonNull NotificationEntry entry) { + final String key = entry.getKey(); + if (isNotificationKeptForRemoteInputHistory(key)) { + mMainHandler.postDelayed(() -> { + if (isNotificationKeptForRemoteInputHistory(key)) { + mNotificationLifetimeFinishedCallback.onSafeToRemove(key); + } + }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); + } + } + + @VisibleForTesting + public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() { + return mEntriesKeptForRemoteInputActive; + } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, + @NonNull String[] args) { + pw.println("LegacyRemoteInputLifetimeExtender:"); + pw.print(" mKeysKeptForRemoteInputHistory: "); + pw.println(mKeysKeptForRemoteInputHistory); + pw.print(" mEntriesKeptForRemoteInputActive: "); + pw.println(mEntriesKeptForRemoteInputActive); + } + + /** + * NotificationRemoteInputManager has multiple reasons to keep notification lifetime + * extended so we implement multiple NotificationLifetimeExtenders + */ + protected abstract class RemoteInputExtender implements NotificationLifetimeExtender { + @Override + public void setCallback(NotificationSafeToRemoveCallback callback) { + if (mNotificationLifetimeFinishedCallback == null) { + mNotificationLifetimeFinishedCallback = callback; + } + } + } + + /** + * Notification is kept alive as it was cancelled in response to a remote input interaction. + * This allows us to show what you replied and allows you to continue typing into it. + */ + protected class RemoteInputHistoryExtender extends RemoteInputExtender { + @Override + public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { + return shouldKeepForRemoteInputHistory(entry); + } + + @Override + public void setShouldManageLifetime(NotificationEntry entry, + boolean shouldExtend) { + if (shouldExtend) { + StatusBarNotification newSbn = mRebuilder.rebuildForRemoteInputReply(entry); + entry.onRemoteInputInserted(); + + if (newSbn == null) { + return; + } + + mEntryManager.updateNotification(newSbn, null); + + // Ensure the entry hasn't already been removed. This can happen if there is an + // inflation exception while updating the remote history + if (entry.isRemoved()) { + return; + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Keeping notification around after sending remote input " + + entry.getKey()); + } + + mKeysKeptForRemoteInputHistory.add(entry.getKey()); + } else { + mKeysKeptForRemoteInputHistory.remove(entry.getKey()); + } + } + } + + /** + * Notification is kept alive for smart reply history. Similar to REMOTE_INPUT_HISTORY but + * with {@link SmartReplyController} specific logic + */ + protected class SmartReplyHistoryExtender extends RemoteInputExtender { + @Override + public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { + return shouldKeepForSmartReplyHistory(entry); + } + + @Override + public void setShouldManageLifetime(NotificationEntry entry, + boolean shouldExtend) { + if (shouldExtend) { + StatusBarNotification newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry); + + if (newSbn == null) { + return; + } + + mEntryManager.updateNotification(newSbn, null); + + if (entry.isRemoved()) { + return; + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Keeping notification around after sending smart reply " + + entry.getKey()); + } + + mKeysKeptForRemoteInputHistory.add(entry.getKey()); + } else { + mKeysKeptForRemoteInputHistory.remove(entry.getKey()); + mSmartReplyController.stopSending(entry); + } + } + } + + /** + * Notification is kept alive because the user is still using the remote input + */ + protected class RemoteInputActiveExtender extends RemoteInputExtender { + @Override + public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { + return isRemoteInputActive(entry); + } + + @Override + public void setShouldManageLifetime(NotificationEntry entry, + boolean shouldExtend) { + if (shouldExtend) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Keeping notification around while remote input active " + + entry.getKey()); + } + mEntriesKeptForRemoteInputActive.add(entry); + } else { + mEntriesKeptForRemoteInputActive.remove(entry); + } + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 5648741e3caf..eb89be1945c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -38,8 +38,8 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.DozeParameters -import com.android.systemui.statusbar.phone.PanelExpansionListener import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.WallpaperController import java.io.FileDescriptor @@ -329,10 +329,12 @@ class NotificationShadeDepthController @Inject constructor( /** * Update blurs when pulling down the shade */ - override fun onPanelExpansionChanged(rawExpansion: Float, tracking: Boolean) { + override fun onPanelExpansionChanged( + rawFraction: Float, expanded: Boolean, tracking: Boolean + ) { val timestamp = SystemClock.elapsedRealtimeNanos() val expansion = MathUtils.saturate( - (rawExpansion - panelPullDownMinFraction) / (1f - panelPullDownMinFraction)) + (rawFraction - panelPullDownMinFraction) / (1f - panelPullDownMinFraction)) if (shadeExpansion == expansion && prevTracking == tracking) { prevTimestamp = timestamp diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 396d86bab825..464b2b69c58e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -27,6 +27,7 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.AssistantFeedbackController; @@ -72,6 +73,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle // Dependencies: private final DynamicChildBindController mDynamicChildBindController; + private final FeatureFlags mFeatureFlags; protected final NotificationLockscreenUserManager mLockscreenUserManager; protected final NotificationGroupManagerLegacy mGroupManager; protected final VisualStabilityManager mVisualStabilityManager; @@ -107,6 +109,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle public NotificationViewHierarchyManager( Context context, @Main Handler mainHandler, + FeatureFlags featureFlags, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationGroupManagerLegacy groupManager, VisualStabilityManager visualStabilityManager, @@ -121,6 +124,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle AssistantFeedbackController assistantFeedbackController) { mContext = context; mHandler = mainHandler; + mFeatureFlags = featureFlags; mLockscreenUserManager = notificationLockscreenUserManager; mBypassController = bypassController; mGroupManager = groupManager; @@ -142,7 +146,9 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle NotificationListContainer listContainer) { mPresenter = presenter; mListContainer = listContainer; - mDynamicPrivacyController.addListener(this); + if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + mDynamicPrivacyController.addListener(this); + } } /** @@ -151,6 +157,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle //TODO: Rewrite this to focus on Entries, or some other data object instead of views public void updateNotificationViews() { Assert.isMainThread(); + if (!mFeatureFlags.checkLegacyPipelineEnabled()) { + return; + } + beginUpdate(); List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications(); @@ -425,6 +435,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle */ public void updateRowStates() { Assert.isMainThread(); + if (!mFeatureFlags.checkLegacyPipelineEnabled()) { + return; + } + beginUpdate(); updateRowStatesInternal(); endUpdate(); @@ -510,6 +524,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle @Override public void onDynamicPrivacyChanged() { + mFeatureFlags.assertLegacyPipelineEnabled(); if (mPerformingUpdate) { Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index 83701a040f24..cde3b0e2e76b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -299,6 +299,9 @@ public class RemoteInputController { default void onRemoteInputSent(NotificationEntry entry) {} } + /** + * This is a delegate which implements some view controller pieces of the remote input process + */ public interface Delegate { /** * Activate remote input if necessary. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java new file mode 100644 index 000000000000..90abec17771c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java @@ -0,0 +1,141 @@ +/* + * 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.systemui.statusbar; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.RemoteInputHistoryItem; +import android.content.Context; +import android.net.Uri; +import android.os.Parcelable; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.util.Arrays; +import java.util.stream.Stream; + +import javax.inject.Inject; + +/** + * A helper class which will augment the notifications using arguments and other information + * accessible to the entry in order to provide intermediate remote input states. + */ +@SysUISingleton +public class RemoteInputNotificationRebuilder { + + private final Context mContext; + + @Inject + RemoteInputNotificationRebuilder(Context context) { + mContext = context; + } + + /** + * When a smart reply is sent off to the app, we insert the text into the remote input history, + * and show a spinner to indicate that the app has yet to respond. + */ + @NonNull + public StatusBarNotification rebuildForSendingSmartReply(NotificationEntry entry, + CharSequence reply) { + return rebuildWithRemoteInputInserted(entry, reply, + true /* showSpinner */, + null /* mimeType */, null /* uri */); + } + + /** + * When the app cancels a notification in response to a smart reply, we remove the spinner + * and leave the previously-added reply. This is the lifetime-extended appearance of the + * notification. + */ + @NonNull + public StatusBarNotification rebuildForCanceledSmartReplies( + NotificationEntry entry) { + return rebuildWithRemoteInputInserted(entry, null /* remoteInputTest */, + false /* showSpinner */, null /* mimeType */, null /* uri */); + } + + /** + * When the app cancels a notification in response to a remote input reply, we update the + * notification with the reply text and/or attachment. This is the lifetime-extended + * appearance of the notification. + */ + @NonNull + public StatusBarNotification rebuildForRemoteInputReply(NotificationEntry entry) { + CharSequence remoteInputText = entry.remoteInputText; + if (TextUtils.isEmpty(remoteInputText)) { + remoteInputText = entry.remoteInputTextWhenReset; + } + String remoteInputMimeType = entry.remoteInputMimeType; + Uri remoteInputUri = entry.remoteInputUri; + StatusBarNotification newSbn = rebuildWithRemoteInputInserted(entry, + remoteInputText, false /* showSpinner */, remoteInputMimeType, + remoteInputUri); + return newSbn; + } + + /** Inner method for generating the SBN */ + @VisibleForTesting + @NonNull + StatusBarNotification rebuildWithRemoteInputInserted(NotificationEntry entry, + CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) { + StatusBarNotification sbn = entry.getSbn(); + + Notification.Builder b = Notification.Builder + .recoverBuilder(mContext, sbn.getNotification().clone()); + if (remoteInputText != null || uri != null) { + RemoteInputHistoryItem newItem = uri != null + ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText) + : new RemoteInputHistoryItem(remoteInputText); + Parcelable[] oldHistoryItems = sbn.getNotification().extras + .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null + ? Stream.concat( + Stream.of(newItem), + Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p)) + .toArray(RemoteInputHistoryItem[]::new) + : new RemoteInputHistoryItem[] { newItem }; + b.setRemoteInputHistory(newHistoryItems); + } + b.setShowRemoteInputSpinner(showSpinner); + b.setHideSmartReplies(true); + + Notification newNotification = b.build(); + + // Undo any compatibility view inflation + newNotification.contentView = sbn.getNotification().contentView; + newNotification.bigContentView = sbn.getNotification().bigContentView; + newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; + + return new StatusBarNotification( + sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), + sbn.getTag(), + sbn.getUid(), + sbn.getInitialPid(), + newNotification, + sbn.getUser(), + sbn.getOverrideGroupKey(), + sbn.getPostTime()); + } + + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java index 7fc18b753d40..e288b1530d4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java @@ -19,35 +19,44 @@ import android.app.Notification; import android.os.RemoteException; import android.util.ArraySet; +import androidx.annotation.NonNull; + import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.Dumpable; +import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Set; /** * Handles when smart replies are added to a notification * and clicked upon. */ -public class SmartReplyController { +public class SmartReplyController implements Dumpable { private final IStatusBarService mBarService; private final NotificationEntryManager mEntryManager; private final NotificationClickNotifier mClickNotifier; - private Set<String> mSendingKeys = new ArraySet<>(); + private final Set<String> mSendingKeys = new ArraySet<>(); private Callback mCallback; /** * Injected constructor. See {@link StatusBarModule}. */ - public SmartReplyController(NotificationEntryManager entryManager, + public SmartReplyController( + DumpManager dumpManager, + NotificationEntryManager entryManager, IStatusBarService statusBarService, NotificationClickNotifier clickNotifier) { mBarService = statusBarService; mEntryManager = entryManager; mClickNotifier = clickNotifier; + dumpManager.registerDumpable(this); } public void setCallback(Callback callback) { @@ -75,6 +84,7 @@ public class SmartReplyController { public void smartActionClicked( NotificationEntry entry, int actionIndex, Notification.Action action, boolean generatedByAssistant) { + // TODO(b/204183781): get this from the current pipeline final int count = mEntryManager.getActiveNotificationsCount(); final int rank = entry.getRanking().getRank(); NotificationVisibility.NotificationLocation location = @@ -112,6 +122,14 @@ public class SmartReplyController { } } + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mSendingKeys: " + mSendingKeys.size()); + for (String key : mSendingKeys) { + pw.println(" * " + key); + } + } + /** * Callback for any class that needs to do something in response to a smart reply being sent. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 6da981b72428..cbb3aba5cc64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -28,7 +28,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.os.SystemProperties; -import android.os.Trace; import android.text.format.DateFormat; import android.util.FloatProperty; import android.util.Log; @@ -182,7 +181,6 @@ public class StatusBarStateControllerImpl implements } synchronized (mListeners) { - Trace.beginSection(TAG + "#setState(" + StatusBarState.toShortString(state) + ")"); String tag = getClass().getSimpleName() + "#setState(" + state + ")"; DejankUtils.startDetectingBlockingIpcs(tag); for (RankedListener rl : new ArrayList<>(mListeners)) { @@ -200,7 +198,6 @@ public class StatusBarStateControllerImpl implements rl.mListener.onStatePostChange(); } DejankUtils.stopDetectingBlockingIpcs(tag); - Trace.endSection(); } return true; @@ -265,14 +262,12 @@ public class StatusBarStateControllerImpl implements mIsDozing = isDozing; synchronized (mListeners) { - Trace.beginSection(TAG + "#setDozing(" + isDozing + ")"); String tag = getClass().getSimpleName() + "#setIsDozing"; DejankUtils.startDetectingBlockingIpcs(tag); for (RankedListener rl : new ArrayList<>(mListeners)) { rl.mListener.onDozingChanged(isDozing); } DejankUtils.stopDetectingBlockingIpcs(tag); - Trace.endSection(); } return true; @@ -338,14 +333,12 @@ public class StatusBarStateControllerImpl implements mDozeAmount = dozeAmount; float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount); synchronized (mListeners) { - Trace.beginSection(TAG + "#setDozeAmount"); String tag = getClass().getSimpleName() + "#setDozeAmount"; DejankUtils.startDetectingBlockingIpcs(tag); for (RankedListener rl : new ArrayList<>(mListeners)) { rl.mListener.onDozeAmountChanged(mDozeAmount, interpolatedAmount); } DejankUtils.stopDetectingBlockingIpcs(tag); - Trace.endSection(); } } @@ -476,13 +469,11 @@ public class StatusBarStateControllerImpl implements public void setPulsing(boolean pulsing) { if (mPulsing != pulsing) { mPulsing = pulsing; - Trace.beginSection(TAG + "#setPulsing(" + pulsing + ")"); synchronized (mListeners) { for (RankedListener rl : new ArrayList<>(mListeners)) { rl.mListener.onPulsingChanged(pulsing); } } - Trace.endSection(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index d297d9581d6a..bb697c3b0851 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; +import com.android.systemui.statusbar.RemoteInputNotificationRebuilder; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -96,9 +97,11 @@ public interface StatusBarDependenciesModule { @Provides static NotificationRemoteInputManager provideNotificationRemoteInputManager( Context context, + FeatureFlags featureFlags, NotificationLockscreenUserManager lockscreenUserManager, SmartReplyController smartReplyController, NotificationEntryManager notificationEntryManager, + RemoteInputNotificationRebuilder rebuilder, Lazy<Optional<StatusBar>> statusBarOptionalLazy, StatusBarStateController statusBarStateController, Handler mainHandler, @@ -108,9 +111,11 @@ public interface StatusBarDependenciesModule { DumpManager dumpManager) { return new NotificationRemoteInputManager( context, + featureFlags, lockscreenUserManager, smartReplyController, notificationEntryManager, + rebuilder, statusBarOptionalLazy, statusBarStateController, mainHandler, @@ -166,10 +171,11 @@ public interface StatusBarDependenciesModule { @SysUISingleton @Provides static SmartReplyController provideSmartReplyController( + DumpManager dumpManager, NotificationEntryManager entryManager, IStatusBarService statusBarService, NotificationClickNotifier clickNotifier) { - return new SmartReplyController(entryManager, statusBarService, clickNotifier); + return new SmartReplyController(dumpManager, entryManager, statusBarService, clickNotifier); } @@ -184,6 +190,7 @@ public interface StatusBarDependenciesModule { static NotificationViewHierarchyManager provideNotificationViewHierarchyManager( Context context, @Main Handler mainHandler, + FeatureFlags featureFlags, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationGroupManagerLegacy groupManager, VisualStabilityManager visualStabilityManager, @@ -199,6 +206,7 @@ public interface StatusBarDependenciesModule { return new NotificationViewHierarchyManager( context, mainHandler, + featureFlags, notificationLockscreenUserManager, groupManager, visualStabilityManager, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt index 589446f3b075..7e4db03148c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -27,7 +27,6 @@ import android.widget.FrameLayout import com.android.systemui.R import com.android.systemui.statusbar.phone.StatusBarLocationPublisher import com.android.systemui.statusbar.phone.StatusBarWindowController -import com.android.systemui.statusbar.phone.StatusBarWindowView import javax.inject.Inject /** @@ -35,7 +34,6 @@ import javax.inject.Inject */ class SystemEventChipAnimationController @Inject constructor( private val context: Context, - private val statusBarWindowView: StatusBarWindowView, private val statusBarWindowController: StatusBarWindowController, private val locationPublisher: StatusBarLocationPublisher ) : SystemStatusChipAnimationCallback { @@ -126,7 +124,7 @@ class SystemEventChipAnimationController @Inject constructor( animationDotView = animationWindowView.findViewById(R.id.dot_view) val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL - statusBarWindowView.addView(animationWindowView, lp) + statusBarWindowController.addViewToWindow(animationWindowView, lp) } private fun start() = if (animationWindowView.isLayoutRtl) right() else left() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index cf9daf60ec2b..4e5bc8e7d099 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -34,7 +34,6 @@ import android.view.View import android.view.ViewGroup import com.android.settingslib.Utils import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -190,24 +189,19 @@ class LockscreenSmartspaceController @Inject constructor( val ssView = plugin.getView(parent) ssView.registerDataProvider(plugin) - val animationController = ActivityLaunchAnimator.Controller.fromView( - ssView as View, - null /* cujType */ - ) - ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter { - override fun startIntent(v: View?, i: Intent?, showOnLockscreen: Boolean) { + override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) { activityStarter.startActivity( - i, + intent, true, /* dismissShade */ - animationController, + null, /* launch animator - looks bad with the transparent smartspace bg */ showOnLockscreen ) } - override fun startPendingIntent(pi: PendingIntent?, showOnLockscreen: Boolean) { + override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) { if (showOnLockscreen) { - pi?.send() + pi.send() } else { activityStarter.startPendingIntentDismissingKeyguard(pi) } 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 60f44a0d4fca..8bc41c20caaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -689,8 +689,9 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPreEntryUpdated(entry); } + final boolean fromSystem = ranking != null; for (NotifCollectionListener listener : mNotifCollectionListeners) { - listener.onEntryUpdated(entry); + listener.onEntryUpdated(entry, fromSystem); } if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index a2c9ffc6bdc4..38b5ee88c5ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -27,8 +27,8 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.KeyguardBypassController -import com.android.systemui.statusbar.phone.PanelExpansionListener import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import javax.inject.Inject @@ -294,8 +294,8 @@ class NotificationWakeUpCoordinator @Inject constructor( this.state = newState } - override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) { - val collapsedEnough = expansion <= 0.9f + override fun onPanelExpansionChanged(fraction: Float, expanded: Boolean, tracking: Boolean) { + val collapsedEnough = fraction <= 0.9f if (collapsedEnough != this.collapsedEnoughToHide) { val couldShowPulsingHuns = canShowPulsingHuns this.collapsedEnoughToHide = collapsedEnough diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index fd0476b76a9a..37eacada19fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -36,7 +36,7 @@ public abstract class ListEntry { private final ListAttachState mPreviousAttachState = ListAttachState.create(); private final ListAttachState mAttachState = ListAttachState.create(); - ListEntry(String key, long creationTime) { + protected ListEntry(String key, long creationTime) { mKey = key; mCreationTime = creationTime; } 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 277b4acb3237..f36f430fc29b 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 @@ -47,7 +47,9 @@ import android.annotation.MainThread; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Notification; +import android.os.Handler; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; @@ -61,6 +63,7 @@ import androidx.annotation.NonNull; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.LogBufferEulogizer; import com.android.systemui.flags.FeatureFlags; @@ -75,6 +78,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.En import com.android.systemui.statusbar.notification.collection.notifcollection.EntryRemovedEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.EntryUpdatedEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.InitEntryEvent; +import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; @@ -130,6 +134,7 @@ public class NotifCollection implements Dumpable { private final SystemClock mClock; private final FeatureFlags mFeatureFlags; private final NotifCollectionLogger mLogger; + private final Handler mMainHandler; private final LogBufferEulogizer mEulogizer; private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>(); @@ -153,6 +158,7 @@ public class NotifCollection implements Dumpable { SystemClock clock, FeatureFlags featureFlags, NotifCollectionLogger logger, + @Main Handler mainHandler, LogBufferEulogizer logBufferEulogizer, DumpManager dumpManager) { Assert.isMainThread(); @@ -160,6 +166,7 @@ public class NotifCollection implements Dumpable { mClock = clock; mFeatureFlags = featureFlags; mLogger = logger; + mMainHandler = mainHandler; mEulogizer = logBufferEulogizer; dumpManager.registerDumpable(TAG, this); @@ -441,7 +448,7 @@ public class NotifCollection implements Dumpable { mEventQueue.add(new BindEntryEvent(entry, sbn)); mLogger.logNotifUpdated(sbn.getKey()); - mEventQueue.add(new EntryUpdatedEvent(entry)); + mEventQueue.add(new EntryUpdatedEvent(entry, true /* fromSystem */)); } } @@ -512,6 +519,7 @@ public class NotifCollection implements Dumpable { } private void dispatchEventsAndRebuildList() { + Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList"); mAmDispatchingToOtherCode = true; while (!mEventQueue.isEmpty()) { mEventQueue.remove().dispatchTo(mNotifCollectionListeners); @@ -521,9 +529,12 @@ public class NotifCollection implements Dumpable { if (mBuildListener != null) { mBuildListener.onBuildList(mReadOnlyNotificationSet); } + Trace.endSection(); } - private void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry) { + private void onEndLifetimeExtension( + @NonNull NotifLifetimeExtender extender, + @NonNull NotificationEntry entry) { Assert.isMainThread(); if (!mAttached) { return; @@ -786,6 +797,51 @@ public class NotifCollection implements Dumpable { private static final String TAG = "NotifCollection"; + /** + * Get an object which can be used to update a notification (internally to the pipeline) + * in response to a user action. + * + * @param name the name of the component that will update notifiations + * @return an updater + */ + public InternalNotifUpdater getInternalNotifUpdater(String name) { + return (sbn, reason) -> mMainHandler.post( + () -> updateNotificationInternally(sbn, name, reason)); + } + + /** + * Provide an updated StatusBarNotification for an existing entry. If no entry exists for the + * given notification key, this method does nothing. + * + * @param sbn the updated notification + * @param name the component which is updating the notification + * @param reason the reason the notification is being updated + */ + private void updateNotificationInternally(StatusBarNotification sbn, String name, + String reason) { + Assert.isMainThread(); + checkForReentrantCall(); + + // Make sure we have the notification to update + NotificationEntry entry = mNotificationSet.get(sbn.getKey()); + if (entry == null) { + mLogger.logNotifInternalUpdateFailed(sbn.getKey(), name, reason); + return; + } + mLogger.logNotifInternalUpdate(sbn.getKey(), name, reason); + + // First do the pieces of postNotification which are not about assuming the notification + // was sent by the app + entry.setSbn(sbn); + mEventQueue.add(new BindEntryEvent(entry, sbn)); + + mLogger.logNotifUpdated(sbn.getKey()); + mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */)); + + // Skip the applyRanking step and go straight to dispatching the events + dispatchEventsAndRebuildList(); + } + @IntDef(prefix = { "REASON_" }, value = { REASON_NOT_CANCELED, REASON_UNKNOWN, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java index 47939f0579f5..27ba4c23db88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection; +import android.os.Handler; + import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; @@ -23,12 +25,14 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; @@ -216,6 +220,22 @@ public class NotifPipeline implements CommonNotifCollection { mShadeListBuilder.addOnBeforeRenderListListener(listener); } + /** Registers an invalidator that can be used to invalidate the entire notif list. */ + public void addPreRenderInvalidator(Invalidator invalidator) { + mShadeListBuilder.addPreRenderInvalidator(invalidator); + } + + /** + * Get an object which can be used to update a notification (internally to the pipeline) + * in response to a user action. + * + * @param name the name of the component that will update notifiations + * @return an updater + */ + public InternalNotifUpdater getInternalNotifUpdater(String name) { + return mNotifCollection.getInternalNotifUpdater(name); + } + /** * Returns a read-only view in to the current shade list, i.e. the list of notifications that * are currently present in the shade. If this method is called during pipeline execution it diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 94ee868ceebc..66d019e778bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -31,7 +31,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; import static java.util.Objects.requireNonNull; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index e26fa045d297..6d38389713a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -30,6 +30,7 @@ import static com.android.systemui.statusbar.notification.collection.listbuilder import android.annotation.MainThread; import android.annotation.Nullable; +import android.os.Trace; import android.util.ArrayMap; import androidx.annotation.NonNull; @@ -45,6 +46,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState; import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; @@ -52,6 +54,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.util.Assert; import com.android.systemui.util.time.SystemClock; @@ -78,6 +81,8 @@ public class ShadeListBuilder implements Dumpable { private final SystemClock mSystemClock; private final ShadeListBuilderLogger mLogger; private final NotificationInteractionTracker mInteractionTracker; + // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated + private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>(); private List<ListEntry> mNotifList = new ArrayList<>(); private List<ListEntry> mNewNotifList = new ArrayList<>(); @@ -171,6 +176,13 @@ public class ShadeListBuilder implements Dumpable { mOnBeforeRenderListListeners.add(listener); } + void addPreRenderInvalidator(Invalidator invalidator) { + Assert.isMainThread(); + + mPipelineState.requireState(STATE_IDLE); + invalidator.setInvalidationListener(this::onPreRenderInvalidated); + } + void addPreGroupFilter(NotifFilter filter) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -253,6 +265,14 @@ public class ShadeListBuilder implements Dumpable { } }; + private void onPreRenderInvalidated(Invalidator invalidator) { + Assert.isMainThread(); + + mLogger.logPreRenderInvalidated(invalidator.getName(), mPipelineState.getState()); + + rebuildListIfBefore(STATE_FINALIZING); + } + private void onPreGroupFilterInvalidated(NotifFilter filter) { Assert.isMainThread(); @@ -313,6 +333,7 @@ public class ShadeListBuilder implements Dumpable { * if we detect that behavior, we should crash instantly. */ private void buildList() { + Trace.beginSection("ShadeListBuilder.buildList"); mPipelineState.requireIsBefore(STATE_BUILD_STARTED); mPipelineState.setState(STATE_BUILD_STARTED); @@ -356,7 +377,7 @@ public class ShadeListBuilder implements Dumpable { // section by our list of custom comparators dispatchOnBeforeSort(mReadOnlyNotifList); mPipelineState.incrementTo(STATE_SORTING); - sortList(); + sortListAndNotifySections(); // Step 7: Lock in our group structure and log anything that's changed since the last run mPipelineState.incrementTo(STATE_FINALIZING); @@ -366,9 +387,11 @@ public class ShadeListBuilder implements Dumpable { // Step 8: Dispatch the new list, first to any listeners and then to the view layer dispatchOnBeforeRenderList(mReadOnlyNotifList); + Trace.beginSection("ShadeListBuilder.onRenderList"); if (mOnRenderListListener != null) { mOnRenderListListener.onRenderList(mReadOnlyNotifList); } + Trace.endSection(); // Step 9: We're done! mLogger.logEndBuildList( @@ -380,6 +403,25 @@ public class ShadeListBuilder implements Dumpable { } mPipelineState.setState(STATE_IDLE); mIterationCount++; + Trace.endSection(); + } + + private void notifySectionEntriesUpdated() { + Trace.beginSection("ShadeListBuilder.notifySectionEntriesUpdated"); + NotifSection currentSection = null; + mTempSectionMembers.clear(); + for (int i = 0; i < mNotifList.size(); i++) { + ListEntry currentEntry = mNotifList.get(i); + if (currentSection != currentEntry.getSection()) { + if (currentSection != null) { + currentSection.getSectioner().onEntriesUpdated(mTempSectionMembers); + mTempSectionMembers.clear(); + } + currentSection = currentEntry.getSection(); + } + mTempSectionMembers.add(currentEntry); + } + Trace.endSection(); } /** @@ -421,6 +463,7 @@ public class ShadeListBuilder implements Dumpable { Collection<? extends ListEntry> entries, List<ListEntry> out, List<NotifFilter> filters) { + Trace.beginSection("ShadeListBuilder.filterNotifs"); final long now = mSystemClock.uptimeMillis(); for (ListEntry entry : entries) { if (entry instanceof GroupEntry) { @@ -452,9 +495,11 @@ public class ShadeListBuilder implements Dumpable { } } } + Trace.endSection(); } private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) { + Trace.beginSection("ShadeListBuilder.groupNotifs"); for (ListEntry listEntry : entries) { // since grouping hasn't happened yet, all notifs are NotificationEntries NotificationEntry entry = (NotificationEntry) listEntry; @@ -510,12 +555,14 @@ public class ShadeListBuilder implements Dumpable { } } } + Trace.endSection(); } private void stabilizeGroupingNotifs(List<ListEntry> topLevelList) { if (mNotifStabilityManager == null) { return; } + Trace.beginSection("ShadeListBuilder.stabilizeGroupingNotifs"); for (int i = 0; i < topLevelList.size(); i++) { final ListEntry tle = topLevelList.get(i); @@ -541,6 +588,7 @@ public class ShadeListBuilder implements Dumpable { } } } + Trace.endSection(); } /** @@ -573,6 +621,7 @@ public class ShadeListBuilder implements Dumpable { } private void promoteNotifs(List<ListEntry> list) { + Trace.beginSection("ShadeListBuilder.promoteNotifs"); for (int i = 0; i < list.size(); i++) { final ListEntry tle = list.get(i); @@ -591,9 +640,11 @@ public class ShadeListBuilder implements Dumpable { }); } } + Trace.endSection(); } private void pruneIncompleteGroups(List<ListEntry> shadeList) { + Trace.beginSection("ShadeListBuilder.pruneIncompleteGroups"); for (int i = 0; i < shadeList.size(); i++) { final ListEntry tle = shadeList.get(i); @@ -648,6 +699,7 @@ public class ShadeListBuilder implements Dumpable { } } } + Trace.endSection(); } /** @@ -713,14 +765,15 @@ public class ShadeListBuilder implements Dumpable { } } - private void sortList() { + private void sortListAndNotifySections() { + Trace.beginSection("ShadeListBuilder.sortListAndNotifySections"); // Assign sections to top-level elements and sort their children for (ListEntry entry : mNotifList) { NotifSection section = applySections(entry); if (entry instanceof GroupEntry) { GroupEntry parent = (GroupEntry) entry; for (NotificationEntry child : parent.getChildren()) { - child.getAttachState().setSection(section); + setEntrySection(child, section); } parent.sortChildren(sChildComparator); } @@ -728,6 +781,10 @@ public class ShadeListBuilder implements Dumpable { // Finally, sort all top-level elements mNotifList.sort(mTopLevelComparator); + + // notify sections since the list is sorted now + notifySectionEntriesUpdated(); + Trace.endSection(); } private void freeEmptyGroups() { @@ -936,11 +993,18 @@ public class ShadeListBuilder implements Dumpable { } } - entry.getAttachState().setSection(finalSection); - + setEntrySection(entry, finalSection); return finalSection; } + private void setEntrySection(ListEntry entry, NotifSection finalSection) { + entry.getAttachState().setSection(finalSection); + NotificationEntry representativeEntry = entry.getRepresentativeEntry(); + if (representativeEntry != null && finalSection != null) { + representativeEntry.setBucket(finalSection.getBucket()); + } + } + @NonNull private NotifSection findSection(ListEntry entry) { for (int i = 0; i < mNotifSections.size(); i++) { @@ -971,27 +1035,35 @@ public class ShadeListBuilder implements Dumpable { } private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) { + Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups"); for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) { mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries); } + Trace.endSection(); } private void dispatchOnBeforeSort(List<ListEntry> entries) { + Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort"); for (int i = 0; i < mOnBeforeSortListeners.size(); i++) { mOnBeforeSortListeners.get(i).onBeforeSort(entries); } + Trace.endSection(); } private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) { + Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter"); for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) { mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries); } + Trace.endSection(); } private void dispatchOnBeforeRenderList(List<ListEntry> entries) { + Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList"); for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) { mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries); } + Trace.endSection(); } @Override @@ -1019,13 +1091,13 @@ public class ShadeListBuilder implements Dumpable { void onRenderList(@NonNull List<ListEntry> entries); } - private static final NotifSectioner DEFAULT_SECTIONER = - new NotifSectioner("UnknownSection") { - @Override - public boolean isInSection(ListEntry entry) { - return true; - } - }; + private static final NotifSectioner DEFAULT_SECTIONER = new NotifSectioner("UnknownSection", + NotificationPriorityBucketKt.BUCKET_UNKNOWN) { + @Override + public boolean isInSection(ListEntry entry) { + return true; + } + }; private static final int MIN_CHILDREN_FOR_GROUP = 2; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java index 3a87f6853bcf..3a39c39cfb20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java @@ -23,13 +23,14 @@ import android.service.notification.StatusBarNotification; import com.android.systemui.ForegroundServiceController; import com.android.systemui.appops.AppOpsController; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; +import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.util.concurrency.DelayableExecutor; import javax.inject.Inject; @@ -47,7 +48,7 @@ import javax.inject.Inject; * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender */ -@SysUISingleton +@CoordinatorScope public class AppOpsCoordinator implements Coordinator { private static final String TAG = "AppOpsCoordinator"; @@ -102,7 +103,8 @@ public class AppOpsCoordinator implements Coordinator { /** * Puts foreground service notifications into its own section. */ - private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService") { + private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService", + NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) { @Override public boolean isInSection(ListEntry entry) { NotificationEntry notificationEntry = entry.getRepresentativeEntry(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java index 29a030f910a4..15f0d885c2fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; @@ -53,7 +53,7 @@ import javax.inject.Inject; * respond to app-cancellations (ie: remove the bubble if the app cancels the notification). * */ -@SysUISingleton +@CoordinatorScope public class BubbleCoordinator implements Coordinator { private static final String TAG = "BubbleCoordinator"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index f0eb084ea8ef..e59f4a62f9b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -16,16 +16,17 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.PeopleHeader import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON +import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE import javax.inject.Inject /** @@ -33,7 +34,7 @@ import javax.inject.Inject * - Elevates important conversation notifications * - Puts conversations into its own people section. @see [NotifCoordinators] for section ordering. */ -@SysUISingleton +@CoordinatorScope class ConversationCoordinator @Inject constructor( private val peopleNotificationIdentifier: PeopleNotificationIdentifier, @PeopleHeader peopleHeaderController: NodeController @@ -45,10 +46,12 @@ class ConversationCoordinator @Inject constructor( } } - val sectioner = object : NotifSectioner("People") { + val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) { override fun isInSection(entry: ListEntry): Boolean = isConversation(entry.representativeEntry!!) - override fun getHeaderNodeController() = peopleHeaderController + override fun getHeaderNodeController() = + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController + if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null } override fun attach(pipeline: NotifPipeline) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java index 47928b42ed5e..e8652493da6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java @@ -23,9 +23,9 @@ import android.content.pm.PackageManager; import android.os.RemoteException; import android.service.notification.StatusBarNotification; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -36,7 +36,7 @@ import javax.inject.Inject; * Special notifications with extra permissions and tags won't be filtered out even when the * device is unprovisioned. */ -@SysUISingleton +@CoordinatorScope public class DeviceProvisionedCoordinator implements Coordinator { private static final String TAG = "DeviceProvisionedCoordinator"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt new file mode 100644 index 000000000000..dbecf1cc28d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt @@ -0,0 +1,126 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import android.util.ArraySet +import com.android.systemui.Dumpable +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager +import com.android.systemui.statusbar.notification.row.NotificationGuts +import com.android.systemui.statusbar.notification.row.NotificationGutsManager +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +private const val TAG = "GutsCoordinator" + +/** + * Coordinates the guts displayed by the [NotificationGutsManager] with the pipeline. + * Specifically, this just adds the lifetime extension necessary to keep guts from disappearing. + */ +@CoordinatorScope +class GutsCoordinator @Inject constructor( + private val notifGutsViewManager: NotifGutsViewManager, + private val logger: GutsCoordinatorLogger, + dumpManager: DumpManager +) : Coordinator, Dumpable { + + /** Keys of any Notifications for which we've been told the guts are open */ + private val notifsWithOpenGuts = ArraySet<String>() + + /** Keys of any Notifications we've extended the lifetime for, based on guts */ + private val notifsExtendingLifetime = ArraySet<String>() + + /** Callback for ending lifetime extension */ + private var onEndLifetimeExtensionCallback: OnEndLifetimeExtensionCallback? = null + + init { + dumpManager.registerDumpable(TAG, this) + } + + override fun attach(pipeline: NotifPipeline) { + notifGutsViewManager.setGutsListener(mGutsListener) + pipeline.addNotificationLifetimeExtender(mLifetimeExtender) + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { + pw.println(" notifsWithOpenGuts: ${notifsWithOpenGuts.size}") + for (key in notifsWithOpenGuts) { + pw.println(" * $key") + } + pw.println(" notifsExtendingLifetime: ${notifsExtendingLifetime.size}") + for (key in notifsExtendingLifetime) { + pw.println(" * $key") + } + pw.println(" onEndLifetimeExtensionCallback: $onEndLifetimeExtensionCallback") + } + + private val mLifetimeExtender: NotifLifetimeExtender = object : NotifLifetimeExtender { + override fun getName(): String { + return TAG + } + + override fun setCallback(callback: OnEndLifetimeExtensionCallback) { + onEndLifetimeExtensionCallback = callback + } + + override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + val isShowingGuts = isCurrentlyShowingGuts(entry) + if (isShowingGuts) { + notifsExtendingLifetime.add(entry.key) + } + return isShowingGuts + } + + override fun cancelLifetimeExtension(entry: NotificationEntry) { + notifsExtendingLifetime.remove(entry.key) + } + } + + private val mGutsListener: NotifGutsViewListener = object : NotifGutsViewListener { + override fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) { + logger.logGutsOpened(entry.key, guts) + if (guts.isLeavebehind) { + // leave-behind guts should not extend the lifetime of the notification + closeGutsAndEndLifetimeExtension(entry) + } else { + notifsWithOpenGuts.add(entry.key) + } + } + + override fun onGutsClose(entry: NotificationEntry) { + logger.logGutsClosed(entry.key) + closeGutsAndEndLifetimeExtension(entry) + } + } + + private fun isCurrentlyShowingGuts(entry: ListEntry) = + notifsWithOpenGuts.contains(entry.key) + + private fun closeGutsAndEndLifetimeExtension(entry: NotificationEntry) { + notifsWithOpenGuts.remove(entry.key) + if (notifsExtendingLifetime.remove(entry.key)) { + onEndLifetimeExtensionCallback?.onEndLifetimeExtension(mLifetimeExtender, entry) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt new file mode 100644 index 000000000000..e8f352f60da0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt @@ -0,0 +1,32 @@ +package com.android.systemui.statusbar.notification.collection.coordinator + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.statusbar.notification.row.NotificationGuts +import javax.inject.Inject + +private const val TAG = "GutsCoordinator" + +class GutsCoordinatorLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + + fun logGutsOpened(key: String, guts: NotificationGuts) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + str2 = guts.gutsContent::class.simpleName + bool1 = guts.isLeavebehind + }, { + "Guts of type $str2 (leave behind: $bool1) opened for class $str1" + }) + } + + fun logGutsClosed(key: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + }, { + "Guts closed for class $str1" + }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java index 6e98c27fe9a9..f8b4274188f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java @@ -19,13 +19,14 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain; -import android.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; @@ -34,6 +35,7 @@ import com.android.systemui.statusbar.notification.collection.render.NodeControl import com.android.systemui.statusbar.notification.dagger.IncomingHeader; import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -55,7 +57,7 @@ import javax.inject.Inject; * * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs. */ -@SysUISingleton +@CoordinatorScope public class HeadsUpCoordinator implements Coordinator { private static final String TAG = "HeadsUpCoordinator"; @@ -163,17 +165,17 @@ public class HeadsUpCoordinator implements Coordinator { private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() { @Override - public String getName() { + public @NonNull String getName() { return TAG; } @Override - public void setCallback(OnEndLifetimeExtensionCallback callback) { + public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) { mEndLifetimeExtension = callback; } @Override - public boolean shouldExtendLifetime(NotificationEntry entry, int reason) { + public boolean shouldExtendLifetime(@NonNull NotificationEntry entry, int reason) { boolean isShowingHun = isCurrentlyShowingHun(entry); if (isShowingHun) { mNotifExtendingLifetime = entry; @@ -182,7 +184,7 @@ public class HeadsUpCoordinator implements Coordinator { } @Override - public void cancelLifetimeExtension(NotificationEntry entry) { + public void cancelLifetimeExtension(@NonNull NotificationEntry entry) { if (Objects.equals(mNotifExtendingLifetime, entry)) { mNotifExtendingLifetime = null; } @@ -196,7 +198,8 @@ public class HeadsUpCoordinator implements Coordinator { } }; - private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp") { + private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp", + NotificationPriorityBucketKt.BUCKET_HEADS_UP) { @Override public boolean isInSection(ListEntry entry) { return isCurrentlyShowingHun(entry); @@ -205,7 +208,11 @@ public class HeadsUpCoordinator implements Coordinator { @Nullable @Override public NodeController getHeaderNodeController() { - return mIncomingHeaderController; + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController + if (RankingCoordinator.SHOW_ALL_SECTIONS) { + return mIncomingHeaderController; + } + return null; } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java index 0059e7baa3c2..6684237c4ebf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java @@ -20,13 +20,21 @@ import static com.android.systemui.statusbar.notification.collection.Notificatio import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import javax.inject.Inject; + /** * Filters out notifications that have been dismissed locally (by the user) but that system server * hasn't yet confirmed the removal of. */ +@CoordinatorScope public class HideLocallyDismissedNotifsCoordinator implements Coordinator { + + @Inject + HideLocallyDismissedNotifsCoordinator() { } + @Override public void attach(NotifPipeline pipeline) { pipeline.addPreGroupFilter(mFilter); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java index e595dd4a2f71..7b5cf8511900 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java @@ -23,6 +23,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import javax.inject.Inject; @@ -37,6 +38,7 @@ import javax.inject.Inject; * TODO: The NotificationLockscreenUserManager currently maintains the list of active user profiles. * We should spin that off into a standalone section at some point. */ +@CoordinatorScope public class HideNotifsForOtherUsersCoordinator implements Coordinator { private final NotificationLockscreenUserManager mLockscreenUserManager; private final SharedCoordinatorLogger mLogger; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index 23d5369833c5..fe1cd7b98cf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -34,13 +34,13 @@ import androidx.annotation.MainThread; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -50,7 +50,7 @@ import javax.inject.Inject; /** * Filters low priority and privacy-sensitive notifications from the lockscreen. */ -@SysUISingleton +@CoordinatorScope public class KeyguardCoordinator implements Coordinator { private static final String TAG = "KeyguardCoordinator"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java index 026a3ffb73cd..8769969834c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java @@ -21,6 +21,7 @@ import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification; import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import javax.inject.Inject; @@ -28,6 +29,7 @@ import javax.inject.Inject; /** * Coordinates hiding (filtering) of media notifications. */ +@CoordinatorScope public class MediaCoordinator implements Coordinator { private static final String TAG = "MediaCoordinator"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java deleted file mode 100644 index 25b201926239..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2019 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.notification.collection.coordinator; - -import com.android.systemui.Dumpable; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -/** - * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the - * Coordinators can register their respective callbacks. - */ -@SysUISingleton -public class NotifCoordinators implements Dumpable { - private static final String TAG = "NotifCoordinators"; - private final List<Coordinator> mCoordinators = new ArrayList<>(); - private final List<NotifSectioner> mOrderedSections = new ArrayList<>(); - - /** - * Creates all the coordinators. - */ - @Inject - public NotifCoordinators( - DumpManager dumpManager, - FeatureFlags featureFlags, - HideNotifsForOtherUsersCoordinator hideNotifsForOtherUsersCoordinator, - KeyguardCoordinator keyguardCoordinator, - RankingCoordinator rankingCoordinator, - AppOpsCoordinator appOpsCoordinator, - DeviceProvisionedCoordinator deviceProvisionedCoordinator, - BubbleCoordinator bubbleCoordinator, - HeadsUpCoordinator headsUpCoordinator, - ConversationCoordinator conversationCoordinator, - PreparationCoordinator preparationCoordinator, - MediaCoordinator mediaCoordinator, - SmartspaceDedupingCoordinator smartspaceDedupingCoordinator, - VisualStabilityCoordinator visualStabilityCoordinator) { - dumpManager.registerDumpable(TAG, this); - - mCoordinators.add(new HideLocallyDismissedNotifsCoordinator()); - mCoordinators.add(hideNotifsForOtherUsersCoordinator); - mCoordinators.add(keyguardCoordinator); - mCoordinators.add(rankingCoordinator); - mCoordinators.add(appOpsCoordinator); - mCoordinators.add(deviceProvisionedCoordinator); - mCoordinators.add(bubbleCoordinator); - mCoordinators.add(conversationCoordinator); - mCoordinators.add(mediaCoordinator); - mCoordinators.add(visualStabilityCoordinator); - - if (featureFlags.isSmartspaceDedupingEnabled()) { - mCoordinators.add(smartspaceDedupingCoordinator); - } - - if (featureFlags.isNewNotifPipelineRenderingEnabled()) { - mCoordinators.add(headsUpCoordinator); - mCoordinators.add(preparationCoordinator); - } - - // Manually add Ordered Sections - // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default - if (featureFlags.isNewNotifPipelineRenderingEnabled()) { - mOrderedSections.add(headsUpCoordinator.getSectioner()); // HeadsUp - } - mOrderedSections.add(appOpsCoordinator.getSectioner()); // ForegroundService - mOrderedSections.add(conversationCoordinator.getSectioner()); // People - mOrderedSections.add(rankingCoordinator.getAlertingSectioner()); // Alerting - mOrderedSections.add(rankingCoordinator.getSilentSectioner()); // Silent - } - - /** - * Sends the pipeline to each coordinator when the pipeline is ready to accept - * {@link Pluggable}s, {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s. - */ - public void attach(NotifPipeline pipeline) { - for (Coordinator c : mCoordinators) { - c.attach(pipeline); - } - - pipeline.setSections(mOrderedSections); - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(); - pw.println(TAG + ":"); - for (Coordinator c : mCoordinators) { - pw.println("\t" + c.getClass()); - } - - for (NotifSectioner s : mOrderedSections) { - pw.println("\t" + s.getName()); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt new file mode 100644 index 000000000000..39b1ec4ff80e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2019 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.notification.collection.coordinator + +import com.android.systemui.Dumpable +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.ArrayList +import javax.inject.Inject + +/** + * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the + * Coordinators can register their respective callbacks. + */ +interface NotifCoordinators : Coordinator, Dumpable + +@CoordinatorScope +class NotifCoordinatorsImpl @Inject constructor( + dumpManager: DumpManager, + featureFlags: FeatureFlags, + hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, + hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, + keyguardCoordinator: KeyguardCoordinator, + rankingCoordinator: RankingCoordinator, + appOpsCoordinator: AppOpsCoordinator, + deviceProvisionedCoordinator: DeviceProvisionedCoordinator, + bubbleCoordinator: BubbleCoordinator, + headsUpCoordinator: HeadsUpCoordinator, + gutsCoordinator: GutsCoordinator, + conversationCoordinator: ConversationCoordinator, + preparationCoordinator: PreparationCoordinator, + mediaCoordinator: MediaCoordinator, + remoteInputCoordinator: RemoteInputCoordinator, + shadeEventCoordinator: ShadeEventCoordinator, + smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, + viewConfigCoordinator: ViewConfigCoordinator, + visualStabilityCoordinator: VisualStabilityCoordinator, + sensitiveContentCoordinator: SensitiveContentCoordinator +) : NotifCoordinators { + + private val mCoordinators: MutableList<Coordinator> = ArrayList() + private val mOrderedSections: MutableList<NotifSectioner> = ArrayList() + + /** + * Creates all the coordinators. + */ + init { + dumpManager.registerDumpable(TAG, this) + mCoordinators.add(hideLocallyDismissedNotifsCoordinator) + mCoordinators.add(hideNotifsForOtherUsersCoordinator) + mCoordinators.add(keyguardCoordinator) + mCoordinators.add(rankingCoordinator) + mCoordinators.add(appOpsCoordinator) + mCoordinators.add(deviceProvisionedCoordinator) + mCoordinators.add(bubbleCoordinator) + mCoordinators.add(conversationCoordinator) + mCoordinators.add(mediaCoordinator) + mCoordinators.add(remoteInputCoordinator) + mCoordinators.add(shadeEventCoordinator) + mCoordinators.add(viewConfigCoordinator) + mCoordinators.add(visualStabilityCoordinator) + mCoordinators.add(sensitiveContentCoordinator) + if (featureFlags.isSmartspaceDedupingEnabled) { + mCoordinators.add(smartspaceDedupingCoordinator) + } + if (featureFlags.isNewNotifPipelineRenderingEnabled) { + mCoordinators.add(headsUpCoordinator) + mCoordinators.add(gutsCoordinator) + mCoordinators.add(preparationCoordinator) + } + + // Manually add Ordered Sections + // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default + if (featureFlags.isNewNotifPipelineRenderingEnabled) { + mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp + } + mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService + mOrderedSections.add(conversationCoordinator.sectioner) // People + mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting + mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent + } + + /** + * Sends the pipeline to each coordinator when the pipeline is ready to accept + * [Pluggable]s, [NotifCollectionListener]s and [NotifLifetimeExtender]s. + */ + override fun attach(pipeline: NotifPipeline) { + for (c in mCoordinators) { + c.attach(pipeline) + } + pipeline.setSections(mOrderedSections) + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { + pw.println() + pw.println("$TAG:") + for (c in mCoordinators) { + pw.println("\t${c.javaClass}") + } + for (s in mOrderedSections) { + pw.println("\t${s.name}") + } + } + + companion object { + private const val TAG = "NotifCoordinators" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 31826c7219de..afdfb3bdeef6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -57,6 +57,7 @@ import javax.inject.Inject; * If a notification was uninflated, this coordinator will filter the notification out from the * {@link ShadeListBuilder} until it is inflated. */ +// TODO(b/204468557): Move to @CoordinatorScope @SysUISingleton public class PreparationCoordinator implements Coordinator { private static final String TAG = "PreparationCoordinator"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 6da4d8b70944..2ab2dd0b1273 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -16,19 +16,24 @@ package com.android.systemui.statusbar.notification.collection.coordinator; +import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; +import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.AlertingHeader; import com.android.systemui.statusbar.notification.dagger.SilentHeader; +import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; + +import java.util.List; import javax.inject.Inject; @@ -39,11 +44,13 @@ import javax.inject.Inject; * - whether the notification's app is suspended or hiding its notifications * - whether DND settings are hiding notifications from ambient display or the notification list */ -@SysUISingleton +@CoordinatorScope public class RankingCoordinator implements Coordinator { + public static final boolean SHOW_ALL_SECTIONS = false; private final StatusBarStateController mStatusBarStateController; private final HighPriorityProvider mHighPriorityProvider; - private final NodeController mSilentHeaderController; + private final NodeController mSilentNodeController; + private final SectionHeaderController mSilentHeaderController; private final NodeController mAlertingHeaderController; @Inject @@ -51,10 +58,12 @@ public class RankingCoordinator implements Coordinator { StatusBarStateController statusBarStateController, HighPriorityProvider highPriorityProvider, @AlertingHeader NodeController alertingHeaderController, - @SilentHeader NodeController silentHeaderController) { + @SilentHeader SectionHeaderController silentHeaderController, + @SilentHeader NodeController silentNodeController) { mStatusBarStateController = statusBarStateController; mHighPriorityProvider = highPriorityProvider; mAlertingHeaderController = alertingHeaderController; + mSilentNodeController = silentNodeController; mSilentHeaderController = silentHeaderController; } @@ -74,7 +83,8 @@ public class RankingCoordinator implements Coordinator { return mSilentNotifSectioner; } - private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting") { + private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting", + NotificationPriorityBucketKt.BUCKET_ALERTING) { @Override public boolean isInSection(ListEntry entry) { return mHighPriorityProvider.isHighPriority(entry); @@ -83,11 +93,16 @@ public class RankingCoordinator implements Coordinator { @Nullable @Override public NodeController getHeaderNodeController() { - return mAlertingHeaderController; + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mAlertingHeaderController + if (SHOW_ALL_SECTIONS) { + return mAlertingHeaderController; + } + return null; } }; - private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent") { + private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent", + NotificationPriorityBucketKt.BUCKET_SILENT) { @Override public boolean isInSection(ListEntry entry) { return !mHighPriorityProvider.isHighPriority(entry); @@ -96,7 +111,19 @@ public class RankingCoordinator implements Coordinator { @Nullable @Override public NodeController getHeaderNodeController() { - return mSilentHeaderController; + return mSilentNodeController; + } + + @Nullable + @Override + public void onEntriesUpdated(@NonNull List<ListEntry> entries) { + for (int i = 0; i < entries.size(); i++) { + if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { + mSilentHeaderController.setClearSectionEnabled(true); + return; + } + } + mSilentHeaderController.setClearSectionEnabled(false); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt new file mode 100644 index 000000000000..3397815f008f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt @@ -0,0 +1,225 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import android.os.Handler +import android.service.notification.NotificationListenerService.REASON_CANCEL +import android.service.notification.NotificationListenerService.REASON_CLICK +import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputListener +import com.android.systemui.statusbar.RemoteInputController +import com.android.systemui.statusbar.RemoteInputNotificationRebuilder +import com.android.systemui.statusbar.SmartReplyController +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.notifcollection.SelfTrackingLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +private const val TAG = "RemoteInputCoordinator" + +/** + * How long to wait before auto-dismissing a notification that was kept for active remote input, and + * has now sent a remote input. We auto-dismiss, because the app may not cannot cancel + * these given that they technically don't exist anymore. We wait a bit in case the app issues + * an update, and to also give the other lifetime extenders a beat to decide they want it. + */ +private const val REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY: Long = 500 + +/** + * How long to wait before releasing a lifetime extension when requested to do so due to a user + * interaction (such as tapping another action). + * We wait a bit in case the app issues an update in response to the action, but not too long or we + * risk appearing unresponsive to the user. + */ +private const val REMOTE_INPUT_EXTENDER_RELEASE_DELAY: Long = 200 + +/** Whether this class should print spammy debug logs */ +private val DEBUG: Boolean by lazy { Log.isLoggable(TAG, Log.DEBUG) } + +@SysUISingleton +class RemoteInputCoordinator @Inject constructor( + dumpManager: DumpManager, + private val mRebuilder: RemoteInputNotificationRebuilder, + private val mNotificationRemoteInputManager: NotificationRemoteInputManager, + @Main private val mMainHandler: Handler, + private val mSmartReplyController: SmartReplyController +) : Coordinator, RemoteInputListener, Dumpable { + + @VisibleForTesting val mRemoteInputHistoryExtender = RemoteInputHistoryExtender() + @VisibleForTesting val mSmartReplyHistoryExtender = SmartReplyHistoryExtender() + @VisibleForTesting val mRemoteInputActiveExtender = RemoteInputActiveExtender() + private val mRemoteInputLifetimeExtenders = listOf( + mRemoteInputHistoryExtender, + mSmartReplyHistoryExtender, + mRemoteInputActiveExtender + ) + + private lateinit var mNotifUpdater: InternalNotifUpdater + + init { + dumpManager.registerDumpable(this) + } + + fun getLifetimeExtenders(): List<NotifLifetimeExtender> = mRemoteInputLifetimeExtenders + + override fun attach(pipeline: NotifPipeline) { + mNotificationRemoteInputManager.setRemoteInputListener(this) + mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) } + mNotifUpdater = pipeline.getInternalNotifUpdater(TAG) + pipeline.addCollectionListener(mCollectionListener) + } + + val mCollectionListener = object : NotifCollectionListener { + override fun onEntryUpdated(entry: NotificationEntry, fromSystem: Boolean) { + if (DEBUG) { + Log.d(TAG, "mCollectionListener.onEntryUpdated(entry=${entry.key}," + + " fromSystem=$fromSystem)") + } + if (fromSystem) { + // Mark smart replies as sent whenever a notification is updated by the app, + // otherwise the smart replies are never marked as sent. + mSmartReplyController.stopSending(entry) + } + } + + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + if (DEBUG) Log.d(TAG, "mCollectionListener.onEntryRemoved(entry=${entry.key})") + // We're removing the notification, the smart reply controller can forget about it. + // TODO(b/145659174): track 'sending' state on the entry to avoid having to clear it. + mSmartReplyController.stopSending(entry) + + // When we know the entry will not be lifetime extended, clean up the remote input view + // TODO: Share code with NotifCollection.cannotBeLifetimeExtended + if (reason == REASON_CANCEL || reason == REASON_CLICK) { + mNotificationRemoteInputManager.cleanUpRemoteInputForUserRemoval(entry) + } + } + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + mRemoteInputLifetimeExtenders.forEach { it.dump(fd, pw, args) } + } + + override fun onRemoteInputSent(entry: NotificationEntry) { + if (DEBUG) Log.d(TAG, "onRemoteInputSent(entry=${entry.key})") + // These calls effectively ensure the freshness of the lifetime extensions. + // NOTE: This is some trickery! By removing the lifetime extensions when we know they should + // be immediately re-upped, we ensure that the side-effects of the lifetime extenders get to + // fire again, thus ensuring that we add subsequent replies to the notification. + mRemoteInputHistoryExtender.endLifetimeExtension(entry.key) + mSmartReplyHistoryExtender.endLifetimeExtension(entry.key) + + // If we're extending for remote input being active, then from the apps point of + // view it is already canceled, so we'll need to cancel it on the apps behalf + // now that a reply has been sent. However, delay so that the app has time to posts an + // update in the mean time, and to give another lifetime extender time to pick it up. + mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key, + REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY) + } + + private fun onSmartReplySent(entry: NotificationEntry, reply: CharSequence) { + if (DEBUG) Log.d(TAG, "onSmartReplySent(entry=${entry.key})") + val newSbn = mRebuilder.rebuildForSendingSmartReply(entry, reply) + mNotifUpdater.onInternalNotificationUpdate(newSbn, + "Adding smart reply spinner for sent") + + // If we're extending for remote input being active, then from the apps point of + // view it is already canceled, so we'll need to cancel it on the apps behalf + // now that a reply has been sent. However, delay so that the app has time to posts an + // update in the mean time, and to give another lifetime extender time to pick it up. + mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key, + REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY) + } + + override fun onPanelCollapsed() { + mRemoteInputActiveExtender.endAllLifetimeExtensions() + } + + override fun isNotificationKeptForRemoteInputHistory(key: String) = + mRemoteInputHistoryExtender.isExtending(key) || + mSmartReplyHistoryExtender.isExtending(key) + + override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) { + if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})") + mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key, + REMOTE_INPUT_EXTENDER_RELEASE_DELAY) + mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key, + REMOTE_INPUT_EXTENDER_RELEASE_DELAY) + mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key, + REMOTE_INPUT_EXTENDER_RELEASE_DELAY) + } + + override fun setRemoteInputController(remoteInputController: RemoteInputController) { + mSmartReplyController.setCallback(this::onSmartReplySent) + } + + @VisibleForTesting + inner class RemoteInputHistoryExtender : + SelfTrackingLifetimeExtender(TAG, "RemoteInputHistory", DEBUG, mMainHandler) { + + override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean = + mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(entry) + + override fun onStartedLifetimeExtension(entry: NotificationEntry) { + val newSbn = mRebuilder.rebuildForRemoteInputReply(entry) + entry.onRemoteInputInserted() + mNotifUpdater.onInternalNotificationUpdate(newSbn, + "Extending lifetime of notification with remote input") + // TODO: Check if the entry was removed due perhaps to an inflation exception? + } + } + + @VisibleForTesting + inner class SmartReplyHistoryExtender : + SelfTrackingLifetimeExtender(TAG, "SmartReplyHistory", DEBUG, mMainHandler) { + + override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean = + mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(entry) + + override fun onStartedLifetimeExtension(entry: NotificationEntry) { + val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry) + mSmartReplyController.stopSending(entry) + mNotifUpdater.onInternalNotificationUpdate(newSbn, + "Extending lifetime of notification with smart reply") + // TODO: Check if the entry was removed due perhaps to an inflation exception? + } + + override fun onCanceledLifetimeExtension(entry: NotificationEntry) { + // TODO(b/145659174): track 'sending' state on the entry to avoid having to clear it. + mSmartReplyController.stopSending(entry) + } + } + + @VisibleForTesting + inner class RemoteInputActiveExtender : + SelfTrackingLifetimeExtender(TAG, "RemoteInputActive", DEBUG, mMainHandler) { + + override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean = + mNotificationRemoteInputManager.isRemoteInputActive(entry) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt new file mode 100644 index 000000000000..a115e0400de3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt @@ -0,0 +1,105 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import android.os.UserHandle +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.notification.DynamicPrivacyController +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator +import dagger.Module +import dagger.Provides + +@Module +object SensitiveContentCoordinatorModule { + @Provides + @JvmStatic + @CoordinatorScope + fun provideCoordinator( + dynamicPrivacyController: DynamicPrivacyController, + lockscreenUserManager: NotificationLockscreenUserManager + ): SensitiveContentCoordinator = + SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager) +} + +/** Coordinates re-inflation and post-processing of sensitive notification content. */ +interface SensitiveContentCoordinator : Coordinator + +private class SensitiveContentCoordinatorImpl( + private val dynamicPrivacyController: DynamicPrivacyController, + private val lockscreenUserManager: NotificationLockscreenUserManager +) : Invalidator("SensitiveContentInvalidator"), + SensitiveContentCoordinator, + DynamicPrivacyController.Listener, + OnBeforeRenderListListener { + + override fun attach(pipeline: NotifPipeline) { + dynamicPrivacyController.addListener(this) + pipeline.addOnBeforeRenderListListener(this) + pipeline.addPreRenderInvalidator(this) + } + + override fun onDynamicPrivacyChanged(): Unit = invalidateList() + + override fun onBeforeRenderList(entries: List<ListEntry>) { + val currentUserId = lockscreenUserManager.currentUserId + val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId) + val deviceSensitive = devicePublic && + !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId) + val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked + for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) { + val notifUserId = entry.sbn.user.identifier + val userLockscreen = devicePublic || + lockscreenUserManager.isLockscreenPublicMode(notifUserId) + val userPublic = when { + // if we're not on the lockscreen, we're definitely private + !userLockscreen -> false + // we are on the lockscreen, so unless we're dynamically unlocked, we're + // definitely public + !dynamicallyUnlocked -> true + // we're dynamically unlocked, but check if the notification needs + // a separate challenge if it's from a work profile + else -> when (notifUserId) { + currentUserId -> false + UserHandle.USER_ALL -> false + else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId) + } + } + val needsRedaction = lockscreenUserManager.needsRedaction(entry) + val isSensitive = userPublic && needsRedaction + entry.setSensitive(isSensitive, deviceSensitive) + } + } +} + +private fun extractAllRepresentativeEntries( + entries: List<ListEntry> +): Sequence<NotificationEntry> = + entries.asSequence().flatMap(::extractAllRepresentativeEntries) + +private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> = + sequence { + listEntry.representativeEntry?.let { yield(it) } + if (listEntry is GroupEntry) { + yieldAll(extractAllRepresentativeEntries(listEntry.children)) + } + }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt new file mode 100644 index 000000000000..2d5c331e2071 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt @@ -0,0 +1,79 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import android.service.notification.NotificationListenerService +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource +import javax.inject.Inject + +/** + * A coordinator which provides callbacks to a view surfaces for various events relevant to the + * shade, such as when the user removes a notification, or when the shade is emptied. + */ +// TODO(b/204468557): Move to @CoordinatorScope +@SysUISingleton +class ShadeEventCoordinator @Inject internal constructor( + private val mLogger: ShadeEventCoordinatorLogger +) : Coordinator, NotifShadeEventSource { + private var mNotifRemovedByUserCallback: Runnable? = null + private var mShadeEmptiedCallback: Runnable? = null + private var mEntryRemoved = false + private var mEntryRemovedByUser = false + + override fun attach(pipeline: NotifPipeline) { + pipeline.addCollectionListener(mNotifCollectionListener) + pipeline.addOnBeforeRenderListListener(this::onBeforeRenderList) + } + + private val mNotifCollectionListener = object : NotifCollectionListener { + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + mEntryRemoved = true + mEntryRemovedByUser = + reason == NotificationListenerService.REASON_CLICK || + reason == NotificationListenerService.REASON_CANCEL_ALL || + reason == NotificationListenerService.REASON_CANCEL + } + } + + override fun setNotifRemovedByUserCallback(callback: Runnable) { + check(mNotifRemovedByUserCallback == null) { "mNotifRemovedByUserCallback already set" } + mNotifRemovedByUserCallback = callback + } + + override fun setShadeEmptiedCallback(callback: Runnable) { + check(mShadeEmptiedCallback == null) { "mShadeEmptiedCallback already set" } + mShadeEmptiedCallback = callback + } + + private fun onBeforeRenderList(entries: List<ListEntry>) { + if (mEntryRemoved && entries.isEmpty()) { + mLogger.logShadeEmptied() + mShadeEmptiedCallback?.run() + } + if (mEntryRemoved && mEntryRemovedByUser) { + mLogger.logNotifRemovedByUser() + mNotifRemovedByUserCallback?.run() + } + mEntryRemoved = false + mEntryRemovedByUser = false + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt new file mode 100644 index 000000000000..c687e1bacbc9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt @@ -0,0 +1,38 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +private const val TAG = "ShadeEventCoordinator" + +/** Logger for the [ShadeEventCoordinator] */ +class ShadeEventCoordinatorLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + + fun logShadeEmptied() { + buffer.log(TAG, LogLevel.DEBUG, { }, { "Shade emptied" }) + } + + fun logNotifRemovedByUser() { + buffer.log(TAG, LogLevel.DEBUG, { }, { "Notification removed by user" }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt index 442d9d2bb205..519d75ff07d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.smartspace.SmartspaceTarget import android.os.Parcelable -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -28,6 +27,7 @@ import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.util.concurrency.DelayableExecutor @@ -45,7 +45,7 @@ import javax.inject.Inject */ // This class is a singleton so that the same instance can be accessed by both the old and new // pipelines -@SysUISingleton +@CoordinatorScope class SmartspaceDedupingCoordinator @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, private val smartspaceController: LockscreenSmartspaceController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt new file mode 100644 index 000000000000..5b86de2a9d87 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt @@ -0,0 +1,109 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import com.android.internal.widget.MessagingGroup +import com.android.internal.widget.MessagingMessage +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener +import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.row.NotificationGutsManager +import com.android.systemui.statusbar.policy.ConfigurationController +import javax.inject.Inject + +/** + * A coordinator which ensures that notifications within the new pipeline are correctly inflated + * for the current uiMode and screen properties; additionally deferring those changes when a user + * change is in progress until that process has completed. + */ +@CoordinatorScope +class ViewConfigCoordinator @Inject internal constructor( + configurationController: ConfigurationController, + lockscreenUserManager: NotificationLockscreenUserManagerImpl, + featureFlags: FeatureFlags, + private val mGutsManager: NotificationGutsManager, + private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor +) : Coordinator, UserChangedListener, ConfigurationController.ConfigurationListener { + + private var mReinflateNotificationsOnUserSwitched = false + private var mDispatchUiModeChangeOnUserSwitched = false + private var mPipeline: NotifPipeline? = null + + init { + if (featureFlags.isNewNotifPipelineRenderingEnabled) { + lockscreenUserManager.addUserChangedListener(this) + configurationController.addCallback(this) + } + } + + override fun attach(pipeline: NotifPipeline) { + mPipeline = pipeline + } + + override fun onDensityOrFontScaleChanged() { + MessagingMessage.dropCache() + MessagingGroup.dropCache() + if (!mKeyguardUpdateMonitor.isSwitchingUser) { + updateNotificationsOnDensityOrFontScaleChanged() + } else { + mReinflateNotificationsOnUserSwitched = true + } + } + + override fun onUiModeChanged() { + if (!mKeyguardUpdateMonitor.isSwitchingUser) { + updateNotificationsOnUiModeChanged() + } else { + mDispatchUiModeChangeOnUserSwitched = true + } + } + + override fun onThemeChanged() { + onDensityOrFontScaleChanged() + } + + override fun onUserChanged(userId: Int) { + if (mReinflateNotificationsOnUserSwitched) { + updateNotificationsOnDensityOrFontScaleChanged() + mReinflateNotificationsOnUserSwitched = false + } + if (mDispatchUiModeChangeOnUserSwitched) { + updateNotificationsOnUiModeChanged() + mDispatchUiModeChangeOnUserSwitched = false + } + } + + private fun updateNotificationsOnUiModeChanged() { + mPipeline?.allNotifs?.forEach { entry -> + val row = entry.row + row?.onUiModeChanged() + } + } + + private fun updateNotificationsOnDensityOrFontScaleChanged() { + mPipeline?.allNotifs?.forEach { entry -> + entry.onDensityOrFontScaleChanged() + val exposedGuts = entry.areGutsExposed() + if (exposedGuts) { + mGutsManager.onDensityOrFontScaleChanged(entry) + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 5d6c0437ce9b..5ba4c2ff36ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -50,6 +50,7 @@ import javax.inject.Inject; * This is now integrated in the data-layer via * {@link com.android.systemui.statusbar.notification.collection.ShadeListBuilder}. */ +// TODO(b/204468557): Move to @CoordinatorScope @SysUISingleton public class VisualStabilityCoordinator implements Coordinator { private final DelayableExecutor mDelayableExecutor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt new file mode 100644 index 000000000000..a26d50d2a059 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt @@ -0,0 +1,65 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator.dagger + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators +import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinatorsImpl +import com.android.systemui.statusbar.notification.collection.coordinator.SensitiveContentCoordinatorModule +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.Subcomponent +import javax.inject.Qualifier +import javax.inject.Scope + +@Module(subcomponents = [CoordinatorsSubcomponent::class]) +object CoordinatorsModule { + @SysUISingleton + @JvmStatic + @Provides + fun notifCoordinators(factory: CoordinatorsSubcomponent.Factory): NotifCoordinators = + factory.create().notifCoordinators +} + +@CoordinatorScope +@Subcomponent(modules = [InternalCoordinatorsModule::class]) +interface CoordinatorsSubcomponent { + @get:Internal val notifCoordinators: NotifCoordinators + + @Subcomponent.Factory + interface Factory { + fun create(): CoordinatorsSubcomponent + } +} + +@Module(includes = [SensitiveContentCoordinatorModule::class]) +private abstract class InternalCoordinatorsModule { + @Binds + @Internal + abstract fun bindNotifCoordinators(impl: NotifCoordinatorsImpl): NotifCoordinators +} + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +private annotation class Internal + +@Scope +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class CoordinatorScope
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java new file mode 100644 index 000000000000..4ee08ed4899f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2010 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.notification.collection.legacy; + +import static com.android.systemui.statusbar.phone.StatusBar.SPEW; + +import android.service.notification.StatusBarNotification; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; + +import org.jetbrains.annotations.NotNull; + +import javax.inject.Inject; + +/** + * This is some logic extracted from the + * {@link com.android.systemui.statusbar.phone.StatusBarNotificationPresenter} + * into a class that implements a new-pipeline interface so that the new pipeline can implement it + * correctly. + * + * Specifically, this is the logic which updates notifications when uiMode and screen properties + * change, and which closes the shade when the last notification disappears. + */ +public class LegacyNotificationPresenterExtensions implements NotifShadeEventSource { + private static final String TAG = "LegacyNotifPresenter"; + private final NotificationEntryManager mEntryManager; + private boolean mEntryListenerAdded; + private Runnable mShadeEmptiedCallback; + private Runnable mNotifRemovedByUserCallback; + + @Inject + public LegacyNotificationPresenterExtensions(NotificationEntryManager entryManager) { + mEntryManager = entryManager; + } + + private void ensureEntryListenerAdded() { + if (mEntryListenerAdded) return; + mEntryListenerAdded = true; + mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { + @Override + public void onEntryRemoved( + @NotNull NotificationEntry entry, + NotificationVisibility visibility, + boolean removedByUser, + int reason) { + StatusBarNotification old = entry.getSbn(); + if (SPEW) { + Log.d(TAG, "removeNotification key=" + entry.getKey() + + " old=" + old + " reason=" + reason); + } + + if (old != null && !mEntryManager.hasActiveNotifications()) { + if (mShadeEmptiedCallback != null) mShadeEmptiedCallback.run(); + } + if (removedByUser) { + if (mNotifRemovedByUserCallback != null) mNotifRemovedByUserCallback.run(); + } + } + }); + } + + @Override + public void setNotifRemovedByUserCallback(@NonNull Runnable callback) { + if (mNotifRemovedByUserCallback != null) { + throw new IllegalStateException("mNotifRemovedByUserCallback already set"); + } + mNotifRemovedByUserCallback = callback; + ensureEntryListenerAdded(); + } + + @Override + public void setShadeEmptiedCallback(@NonNull Runnable callback) { + if (mShadeEmptiedCallback != null) { + throw new IllegalStateException("mShadeEmptiedCallback already set"); + } + mShadeEmptiedCallback = callback; + ensureEntryListenerAdded(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt index c9fc9929f0d3..6424e37ad328 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt @@ -18,14 +18,17 @@ package com.android.systemui.statusbar.notification.collection.listbuilder import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.stack.PriorityBucket data class NotifSection( val sectioner: NotifSectioner, val index: Int ) { val label: String - get() = "Section($index, \"${sectioner.name}\")" + get() = "Section($index, $bucket, \"${sectioner.name}\")" val headerController: NodeController? get() = sectioner.headerNodeController + + @PriorityBucket val bucket: Int = sectioner.bucket } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index 5a35127397b4..8fff90504798 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -47,6 +47,15 @@ class ShadeListBuilderLogger @Inject constructor( }) } + fun logPreRenderInvalidated(filterName: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = filterName + int1 = pipelineState + }, { + """Pre-render Invalidator "$str1" invalidated; pipeline state is $int1""" + }) + } + fun logPreGroupFilterInvalidated(filterName: String, pipelineState: Int) { buffer.log(TAG, DEBUG, { str1 = filterName diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java new file mode 100644 index 000000000000..d7092ecd536e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java @@ -0,0 +1,24 @@ +/* + * 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.systemui.statusbar.notification.collection.listbuilder.pluggable; + +/** A {@link Pluggable} that can only invalidate. */ +public abstract class Invalidator extends Pluggable<Invalidator> { + protected Invalidator(String name) { + super(name); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java index c8982d35c4a0..ef9ee11ef116 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java @@ -22,13 +22,28 @@ import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.render.NodeController; import com.android.systemui.statusbar.notification.collection.render.NodeSpec; +import com.android.systemui.statusbar.notification.stack.PriorityBucket; + +import java.util.List; /** - * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}. + * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSectioners}. */ public abstract class NotifSectioner extends Pluggable<NotifSectioner> { - protected NotifSectioner(String name) { + @PriorityBucket + private final int mBucket; + + protected NotifSectioner(String name, @PriorityBucket int bucket) { super(name); + mBucket = bucket; + } + + /** + * @return the "bucket" value to apply to entries in this section + */ + @PriorityBucket + public final int getBucket() { + return mBucket; } /** @@ -46,4 +61,10 @@ public abstract class NotifSectioner extends Pluggable<NotifSectioner> { public @Nullable NodeController getHeaderNodeController() { return null; } + + /** + * Notify of children of this section being updated + * @param entries of this section that are borrowed (must clone to store) + */ + public void onEntriesUpdated(List<ListEntry> entries) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java index 8e4fb7523767..b981a9621526 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; import android.annotation.Nullable; +import android.os.Trace; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -50,7 +51,9 @@ public abstract class Pluggable<This> { */ public final void invalidateList() { if (mListener != null) { + Trace.beginSection("Pluggable<" + mName + ">.invalidateList"); mListener.onPluggableInvalidated((This) this); + Trace.endSection(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/InternalNotifUpdater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/InternalNotifUpdater.java new file mode 100644 index 000000000000..5692fb2b523e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/InternalNotifUpdater.java @@ -0,0 +1,37 @@ +/* + * 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.systemui.statusbar.notification.collection.notifcollection; + +import android.service.notification.StatusBarNotification; + +/** + * An object that allows Coordinators to update notifications internally to SystemUI. + * This is used when part of the UI involves updating the underlying appearance of a notification + * on behalf of an app, such as to add a spinner or remote input history. + */ +public interface InternalNotifUpdater { + /** + * Called when an already-existing notification needs to be updated to a new temporary + * appearance. + * This update is local to the SystemUI process. + * This has no effect if no notification with the given key exists in the pipeline. + * + * @param sbn a notification to update + * @param reason a debug reason for the update + */ + void onInternalNotificationUpdate(StatusBarNotification sbn, String reason); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index db0c1745f565..68a346f817e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -56,6 +56,17 @@ public interface NotifCollectionListener { /** * Called whenever a notification with the same key as an existing notification is posted. By * the time this listener is called, the entry's SBN and Ranking will already have been updated. + * This delegates to {@link #onEntryUpdated(NotificationEntry)} by default. + * @param fromSystem If true, this update came from the NotificationManagerService. + * If false, the notification update is an internal change within systemui. + */ + default void onEntryUpdated(@NonNull NotificationEntry entry, boolean fromSystem) { + onEntryUpdated(entry); + } + + /** + * Called whenever a notification with the same key as an existing notification is posted. By + * the time this listener is called, the entry's SBN and Ranking will already have been updated. */ default void onEntryUpdated(@NonNull NotificationEntry entry) { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index f8a778d6b1d2..1ebc66e4c665 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -121,6 +121,26 @@ class NotifCollectionLogger @Inject constructor( }) } + fun logNotifInternalUpdate(key: String, name: String, reason: String) { + buffer.log(TAG, INFO, { + str1 = key + str2 = name + str3 = reason + }, { + "UPDATED INTERNALLY $str1 BY $str2 BECAUSE $str3" + }) + } + + fun logNotifInternalUpdateFailed(key: String, name: String, reason: String) { + buffer.log(TAG, INFO, { + str1 = key + str2 = name + str3 = reason + }, { + "FAILED INTERNAL UPDATE $str1 BY $str2 BECAUSE $str3" + }) + } + fun logNoNotificationToRemoveWithKey(key: String) { buffer.log(TAG, ERROR, { str1 = key diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt index 2810b891373f..179e95328442 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt @@ -64,10 +64,11 @@ data class EntryAddedEvent( } data class EntryUpdatedEvent( - val entry: NotificationEntry + val entry: NotificationEntry, + val fromSystem: Boolean ) : NotifEvent() { override fun dispatchToListener(listener: NotifCollectionListener) { - listener.onEntryUpdated(entry) + listener.onEntryUpdated(entry, fromSystem) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java index f8fe0676e003..2fe3bd63c2e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; +import androidx.annotation.NonNull; + import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -26,14 +28,14 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; */ public interface NotifLifetimeExtender { /** Name to associate with this extender (for the purposes of debugging) */ - String getName(); + @NonNull String getName(); /** * Called on the extender immediately after it has been registered. The extender should hang on * to this callback and execute it whenever it no longer needs to extend the lifetime of a * notification. */ - void setCallback(OnEndLifetimeExtensionCallback callback); + void setCallback(@NonNull OnEndLifetimeExtensionCallback callback); /** * Called by the NotifCollection whenever a notification has been retracted (by the app) or @@ -43,7 +45,7 @@ public interface NotifLifetimeExtender { * called on all lifetime extenders even if earlier ones return true (in other words, multiple * lifetime extenders can be extending a notification at the same time). */ - boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason); + boolean shouldExtendLifetime(@NonNull NotificationEntry entry, @CancellationReason int reason); /** * Called by the NotifCollection to inform a lifetime extender that its extension of a notif @@ -51,7 +53,7 @@ public interface NotifLifetimeExtender { * lifetime extension). The extender should clean up any references it has to the notif in * question. */ - void cancelLifetimeExtension(NotificationEntry entry); + void cancelLifetimeExtension(@NonNull NotificationEntry entry); /** Callback for notifying the NotifCollection that a lifetime extension has expired.*/ interface OnEndLifetimeExtensionCallback { @@ -59,6 +61,8 @@ public interface NotifLifetimeExtender { * Stop extending the lifetime of `entry` with `extender` and then immediately re-evaluates * whether to continue lifetime extending this notification or to remove it. */ - void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry); + void onEndLifetimeExtension( + @NonNull NotifLifetimeExtender extender, + @NonNull NotificationEntry entry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt new file mode 100644 index 000000000000..145c1e54d732 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt @@ -0,0 +1,113 @@ +package com.android.systemui.statusbar.notification.collection.notifcollection + +import android.os.Handler +import android.util.ArrayMap +import android.util.Log +import com.android.systemui.Dumpable +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import java.io.FileDescriptor +import java.io.PrintWriter + +/** + * A helpful class that implements the core contract of the lifetime extender internally, + * making it easier for coordinators to interact with them + */ +abstract class SelfTrackingLifetimeExtender( + private val tag: String, + private val name: String, + private val debug: Boolean, + private val mainHandler: Handler +) : NotifLifetimeExtender, Dumpable { + private lateinit var mCallback: NotifLifetimeExtender.OnEndLifetimeExtensionCallback + protected val mEntriesExtended = ArrayMap<String, NotificationEntry>() + private var mEnding = false + + /** + * When debugging, warn if the call is happening during and "end lifetime extension" call. + * + * Note: this will warn a lot! The pipeline explicitly re-invokes all lifetime extenders + * whenever one ends, giving all of them a chance to re-up their lifetime extension. + */ + private fun warnIfEnding() { + if (debug && mEnding) Log.w(tag, "reentrant code while ending a lifetime extension") + } + + fun endAllLifetimeExtensions() { + // clear the map before iterating over a copy of the items, because the pipeline will + // always give us another chance to extend the lifetime again, and we don't want + // concurrent modification + val entries = mEntriesExtended.values.toList() + if (debug) Log.d(tag, "$name.endAllLifetimeExtensions() entries=$entries") + mEntriesExtended.clear() + warnIfEnding() + mEnding = true + entries.forEach { mCallback.onEndLifetimeExtension(this, it) } + mEnding = false + } + + fun endLifetimeExtensionAfterDelay(key: String, delayMillis: Long) { + if (debug) { + Log.d(tag, "$name.endLifetimeExtensionAfterDelay" + + "(key=$key, delayMillis=$delayMillis)" + + " isExtending=${isExtending(key)}") + } + if (isExtending(key)) { + mainHandler.postDelayed({ endLifetimeExtension(key) }, delayMillis) + } + } + + fun endLifetimeExtension(key: String) { + if (debug) { + Log.d(tag, "$name.endLifetimeExtension(key=$key)" + + " isExtending=${isExtending(key)}") + } + warnIfEnding() + mEnding = true + mEntriesExtended.remove(key)?.let { removedEntry -> + mCallback.onEndLifetimeExtension(this, removedEntry) + } + mEnding = false + } + + fun isExtending(key: String) = mEntriesExtended.contains(key) + + final override fun getName(): String = name + + final override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + val shouldExtend = queryShouldExtendLifetime(entry) + if (debug) { + Log.d(tag, "$name.shouldExtendLifetime(key=${entry.key}, reason=$reason)" + + " isExtending=${isExtending(entry.key)}" + + " shouldExtend=$shouldExtend") + } + warnIfEnding() + if (shouldExtend && mEntriesExtended.put(entry.key, entry) == null) { + onStartedLifetimeExtension(entry) + } + return shouldExtend + } + + final override fun cancelLifetimeExtension(entry: NotificationEntry) { + if (debug) { + Log.d(tag, "$name.cancelLifetimeExtension(key=${entry.key})" + + " isExtending=${isExtending(entry.key)}") + } + warnIfEnding() + mEntriesExtended.remove(entry.key) + onCanceledLifetimeExtension(entry) + } + + abstract fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean + open fun onStartedLifetimeExtension(entry: NotificationEntry) {} + open fun onCanceledLifetimeExtension(entry: NotificationEntry) {} + + final override fun setCallback(callback: NotifLifetimeExtender.OnEndLifetimeExtensionCallback) { + mCallback = callback + } + + final override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + pw.println("LifetimeExtender: $name:") + pw.println(" mEntriesExtended: ${mEntriesExtended.size}") + mEntriesExtended.forEach { pw.println(" * ${it.key}") } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index 9b8ac722d5c9..010b6f80121c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -20,6 +20,7 @@ import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.util.traceSection /** * Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract @@ -36,7 +37,7 @@ class NodeSpecBuilder( fun buildNodeSpec( rootController: NodeController, notifList: List<ListEntry> - ): NodeSpec { + ): NodeSpec = traceSection("NodeSpecBuilder.buildNodeSpec") { val root = NodeSpecImpl(null, rootController) var currentSection: NotifSection? = null val prevSections = mutableSetOf<NotifSection?>() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt new file mode 100644 index 000000000000..129f6b1750e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt @@ -0,0 +1,30 @@ +/* + * 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.systemui.statusbar.notification.collection.render + +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.row.NotificationGuts + +/** + * Interface for listening to guts open and close events. + */ +interface NotifGutsViewListener { + /** A notification's guts are being opened */ + fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) + + /** A notification's guts are being closed */ + fun onGutsClose(entry: NotificationEntry) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt new file mode 100644 index 000000000000..0260f89110f8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt @@ -0,0 +1,24 @@ +/* + * 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.systemui.statusbar.notification.collection.render + +/** A type which provides open and close guts events to a single listener */ +interface NotifGutsViewManager { + /** + * @param listener the object that will listen to open and close guts events + */ + fun setGutsListener(listener: NotifGutsViewListener?) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt new file mode 100644 index 000000000000..e24f6a036095 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt @@ -0,0 +1,35 @@ +/* + * 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.systemui.statusbar.notification.collection.render + +/** + * This is an object which provides callbacks for certain important events related to the + * notification shade, such as notifications being removed by the user, or the shade becoming empty. + */ +interface NotifShadeEventSource { + /** + * Registers a callback to be invoked when the last notification has been removed from + * the shade for any reason + */ + fun setShadeEmptiedCallback(callback: Runnable) + + /** + * Registers a callback to be invoked when a notification has been removed from + * the shade by a user action + */ + fun setNotifRemovedByUserCallback(callback: Runnable) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt index 1311e3e756dc..8c15647c5038 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt @@ -33,7 +33,8 @@ import javax.inject.Inject interface SectionHeaderController { fun reinflateView(parent: ViewGroup) val headerView: SectionHeaderView? - fun setOnClearAllClickListener(listener: View.OnClickListener) + fun setClearSectionEnabled(enabled: Boolean) + fun setOnClearSectionClickListener(listener: View.OnClickListener) } @SectionHeaderScope @@ -46,6 +47,7 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor( ) : NodeController, SectionHeaderController { private var _view: SectionHeaderView? = null + private var clearAllButtonEnabled = false private var clearAllClickListener: View.OnClickListener? = null private val onHeaderClickListener = View.OnClickListener { activityStarter.startActivity( @@ -76,12 +78,18 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor( parent.addView(inflated, oldPos) } _view = inflated + _view?.setClearSectionButtonEnabled(clearAllButtonEnabled) } override val headerView: SectionHeaderView? get() = _view - override fun setOnClearAllClickListener(listener: View.OnClickListener) { + override fun setClearSectionEnabled(enabled: Boolean) { + clearAllButtonEnabled = enabled + _view?.setClearSectionButtonEnabled(enabled) + } + + override fun setOnClearSectionClickListener(listener: View.OnClickListener) { clearAllClickListener = listener _view?.setOnClearAllClickListener(listener) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt index 7babbb40b6c1..6d4ae4b1a869 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render import android.annotation.MainThread import android.view.View import com.android.systemui.util.kotlin.transform +import com.android.systemui.util.traceSection /** * Given a "spec" that describes a "tree" of views, adds and removes views from the @@ -47,7 +48,7 @@ class ShadeViewDiffer( * provided [spec]. The root node of the spec must match the root controller passed to the * differ's constructor. */ - fun applySpec(spec: NodeSpec) { + fun applySpec(spec: NodeSpec) = traceSection("ShadeViewDiffer.applySpec") { val specMap = treeToMap(spec) if (spec.controller != rootNode.controller) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index a2c7aa5cc8f7..b582a24f5a3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -18,10 +18,13 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context import android.view.View +import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.ShadeListBuilder +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.NotificationIconAreaController +import com.android.systemui.util.traceSection import javax.inject.Inject /** @@ -32,7 +35,7 @@ class ShadeViewManager constructor( context: Context, listContainer: NotificationListContainer, logger: ShadeViewDifferLogger, - viewBarn: NotifViewBarn, + private val viewBarn: NotifViewBarn, private val notificationIconAreaController: NotificationIconAreaController ) { // We pass a shim view here because the listContainer may not actually have a view associated @@ -45,8 +48,21 @@ class ShadeViewManager constructor( listBuilder.setOnRenderListListener(::onNewNotifTree) private fun onNewNotifTree(notifList: List<ListEntry>) { - viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList)) - notificationIconAreaController.updateNotificationIcons(notifList) + traceSection("ShadeViewManager.onNewNotifTree") { + viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList)) + updateGroupCounts(notifList) + notificationIconAreaController.updateNotificationIcons(notifList) + } + } + + private fun updateGroupCounts(notifList: List<ListEntry>) { + traceSection("ShadeViewManager.updateGroupCounts") { + notifList.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry -> + val controller = viewBarn.requireView(checkNotNull(groupEntry.summary)) + val row = controller.view as ExpandableNotificationRow + row.setUntruncatedChildCount(groupEntry.untruncatedChildCount) + } + } } } 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 94f5c44d7c78..1eb007e345ec 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 @@ -45,10 +45,13 @@ import com.android.systemui.statusbar.notification.NotificationEntryManagerLogge import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl; +import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; @@ -58,6 +61,8 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl; +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; @@ -89,7 +94,10 @@ import dagger.Provides; /** * Dagger Module for classes found within the com.android.systemui.statusbar.notification package. */ -@Module(includes = { NotificationSectionHeadersModule.class }) +@Module(includes = { + NotificationSectionHeadersModule.class, + CoordinatorsModule.class +}) public interface NotificationsModule { @Binds StackScrollAlgorithm.SectionProvider bindSectionProvider( @@ -169,6 +177,14 @@ public interface NotificationsModule { dumpManager); } + /** Provides an instance of {@link NotifGutsViewManager} */ + @SysUISingleton + @Provides + static NotifGutsViewManager provideNotifGutsViewManager( + NotificationGutsManager notificationGutsManager) { + return notificationGutsManager; + } + /** Provides an instance of {@link VisualStabilityManager} */ @SysUISingleton @Provides @@ -262,6 +278,20 @@ public interface NotificationsModule { } /** + * Provide the active implementation for presenting notifications. + */ + @Provides + @SysUISingleton + static NotifShadeEventSource provideNotifShadeEventSource( + FeatureFlags featureFlags, + Lazy<ShadeEventCoordinator> shadeEventCoordinatorLazy, + Lazy<LegacyNotificationPresenterExtensions> legacyNotificationPresenterExtensionsLazy) { + return featureFlags.isNewNotifPipelineRenderingEnabled() + ? shadeEventCoordinatorLazy.get() + : legacyNotificationPresenterExtensionsLazy.get(); + } + + /** * Provide a dismissal callback that's triggered when a user manually dismissed a notification * from the notification shade or it gets auto-cancelled by click. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java index c147023edf8d..9faef1b43bc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java @@ -16,12 +16,12 @@ package com.android.systemui.statusbar.notification.logging; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import android.annotation.Nullable; import android.service.notification.StatusBarNotification; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 0d8e85094646..1bbd45192bea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -107,6 +107,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; +import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.wmshell.BubblesManager; import java.io.FileDescriptor; @@ -1249,6 +1250,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.onConfigurationChanged(); } @@ -1758,7 +1760,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Called when a notification is dropped on proper target window. */ public void dragAndDropSuccess() { - mOnDragSuccessListener.onDragSuccess(getEntry()); + if (mOnDragSuccessListener != null) { + mOnDragSuccessListener.onDragSuccess(getEntry()); + } } private void doLongClickCallback() { @@ -3201,13 +3205,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */ - public void setDismissRtl(boolean dismissRtl) { - if (mMenuRow != null) { - mMenuRow.setDismissRtl(dismissRtl); - } - } - private static class NotificationViewState extends ExpandableViewState { @Override @@ -3311,40 +3308,45 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - super.dump(fd, pw, args); - pw.println(" Notification: " + mEntry.getKey()); - pw.print(" visibility: " + getVisibility()); - pw.print(", alpha: " + getAlpha()); - pw.print(", translation: " + getTranslation()); - pw.print(", removed: " + isRemoved()); - pw.print(", expandAnimationRunning: " + mExpandAnimationRunning); - NotificationContentView showingLayout = getShowingLayout(); - pw.print(", privateShowing: " + (showingLayout == mPrivateLayout)); - pw.println(); - showingLayout.dump(fd, pw, args); - pw.print(" "); - if (getViewState() != null) { - getViewState().dump(fd, pw, args); - } else { - pw.print("no viewState!!!"); - } - pw.println(); - pw.println(); - if (mIsSummaryWithChildren) { - pw.print(" ChildrenContainer"); - pw.print(" visibility: " + mChildrenContainer.getVisibility()); - pw.print(", alpha: " + mChildrenContainer.getAlpha()); - pw.print(", translationY: " + mChildrenContainer.getTranslationY()); - pw.println(); - List<ExpandableNotificationRow> notificationChildren = getAttachedChildren(); - pw.println(" Children: " + notificationChildren.size()); - pw.println(" {"); - for(ExpandableNotificationRow child : notificationChildren) { - child.dump(fd, pw, args); - } - pw.println(" }"); - pw.println(); - } + // Skip super call; dump viewState ourselves + pw.println("Notification: " + mEntry.getKey()); + DumpUtilsKt.withIndenting(pw, ipw -> { + ipw.print("visibility: " + getVisibility()); + ipw.print(", alpha: " + getAlpha()); + ipw.print(", translation: " + getTranslation()); + ipw.print(", removed: " + isRemoved()); + ipw.print(", expandAnimationRunning: " + mExpandAnimationRunning); + NotificationContentView showingLayout = getShowingLayout(); + ipw.print(", privateShowing: " + (showingLayout == mPrivateLayout)); + ipw.println(); + showingLayout.dump(fd, ipw, args); + + if (getViewState() != null) { + getViewState().dump(fd, ipw, args); + ipw.println(); + } else { + ipw.println("no viewState!!!"); + } + + if (mIsSummaryWithChildren) { + ipw.println(); + ipw.print("ChildrenContainer"); + ipw.print(" visibility: " + mChildrenContainer.getVisibility()); + ipw.print(", alpha: " + mChildrenContainer.getAlpha()); + ipw.print(", translationY: " + mChildrenContainer.getTranslationY()); + ipw.println(); + List<ExpandableNotificationRow> notificationChildren = getAttachedChildren(); + ipw.println("Children: " + notificationChildren.size()); + ipw.print("{"); + ipw.increaseIndent(); + for (ExpandableNotificationRow child : notificationChildren) { + ipw.println(); + child.dump(fd, ipw, args); + } + ipw.decreaseIndent(); + ipw.println("}"); + } + }); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 8b0764b1c313..fa2c1ee772cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -34,6 +34,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; +import com.android.systemui.util.DumpUtilsKt; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -743,6 +744,16 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(getClass().getSimpleName()); + DumpUtilsKt.withIndenting(pw, ipw -> { + ExpandableViewState viewState = getViewState(); + if (viewState == null) { + ipw.println("no viewState!!!"); + } else { + viewState.dump(fd, ipw, args); + ipw.println(); + } + }); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 9eb95c409009..b27a40a828f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -25,6 +25,10 @@ import android.view.View; import com.android.systemui.R; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ViewState; +import com.android.systemui.util.DumpUtilsKt; + +import java.io.FileDescriptor; +import java.io.PrintWriter; public class FooterView extends StackScrollerDecorView { private FooterViewButton mDismissButton; @@ -45,6 +49,19 @@ public class FooterView extends StackScrollerDecorView { } @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dump(fd, pw, args); + DumpUtilsKt.withIndenting(pw, ipw -> { + ipw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility())); + ipw.println("manageButton showHistory: " + mShowHistory); + ipw.println("manageButton visibility: " + + DumpUtilsKt.visibilityString(mDismissButton.getVisibility())); + ipw.println("dismissButton visibility: " + + DumpUtilsKt.visibilityString(mDismissButton.getVisibility())); + }); + } + + @Override protected void onFinishInflate() { super.onFinishInflate(); mDismissButton = (FooterViewButton) findSecondaryView(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 4f54e4feb21d..df484dd8ed77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1253,7 +1253,7 @@ public class NotificationContentView extends FrameLayout { } if (hasRemoteInput) { existing.setWrapper(wrapper); - existing.setOnVisibilityChangedListener(this::setRemoteInputVisible); + existing.addOnVisibilityChangedListener(this::setRemoteInputVisible); if (existingPendingIntent != null || existing.isActive()) { // The current action could be gone, or the pending intent no longer valid. @@ -1938,7 +1938,6 @@ public class NotificationContentView extends FrameLayout { } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.print(" "); pw.print("contentView visibility: " + getVisibility()); pw.print(", alpha: " + getAlpha()); pw.print(", clipBounds: " + getClipBounds()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 7eec95acc6ec..8e02d9f635d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -65,6 +65,8 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener; +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -83,7 +85,8 @@ import dagger.Lazy; * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and * closing guts, and keeping track of the currently exposed notification guts. */ -public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender { +public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender, + NotifGutsViewManager { private static final String TAG = "NotificationGutsManager"; // Must match constant in Settings. Used to highlight preferences when linking to Settings. @@ -123,7 +126,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final Optional<BubblesManager> mBubblesManagerOptional; private Runnable mOpenRunnable; private final INotificationManager mNotificationManager; - private final NotificationEntryManager mNotificationEntryManager; private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; private final LauncherApps mLauncherApps; private final ShortcutManager mShortcutManager; @@ -131,6 +133,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final UiEventLogger mUiEventLogger; private final ShadeController mShadeController; private final AppWidgetManager mAppWidgetManager; + private NotifGutsViewListener mGutsListener; /** * Injected constructor. See {@link NotificationsModule}. @@ -161,7 +164,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mAccessibilityManager = accessibilityManager; mHighPriorityProvider = highPriorityProvider; mNotificationManager = notificationManager; - mNotificationEntryManager = notificationEntryManager; mPeopleSpaceWidgetManager = peopleSpaceWidgetManager; mLauncherApps = launcherApps; mShortcutManager = shortcutManager; @@ -258,10 +260,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @VisibleForTesting protected boolean bindGuts(final ExpandableNotificationRow row, NotificationMenuRowPlugin.MenuItem item) { - StatusBarNotification sbn = row.getEntry().getSbn(); + NotificationEntry entry = row.getEntry(); row.setGutsView(item); - row.setTag(sbn.getPackageName()); + row.setTag(entry.getSbn().getPackageName()); row.getGuts().setClosedListener((NotificationGuts g) -> { row.onGutsClosed(); if (!g.willBeRemoved() && !row.isRemoved()) { @@ -272,7 +274,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mNotificationGutsExposed = null; mGutsMenuItem = null; } - String key = sbn.getKey(); + if (mGutsListener != null) { + mGutsListener.onGutsClose(entry); + } + String key = entry.getKey(); if (key.equals(mKeyToRemoveOnGutsClosed)) { mKeyToRemoveOnGutsClosed = null; if (mNotificationLifetimeFinishedCallback != null) { @@ -650,6 +655,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx needsFalsingProtection, row::onGutsOpened); + if (mGutsListener != null) { + mGutsListener.onGutsOpen(row.getEntry(), guts); + } + row.closeRemoteInput(); mListContainer.onHeightChanged(row, true /* needsAnimation */); mGutsMenuItem = menuItem; @@ -695,10 +704,17 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationGutsManager state:"); - pw.print(" mKeyToRemoveOnGutsClosed: "); + pw.print(" mKeyToRemoveOnGutsClosed (legacy): "); pw.println(mKeyToRemoveOnGutsClosed); } + /** + * @param gutsListener the listener for open and close guts events + */ + public void setGutsListener(NotifGutsViewListener gutsListener) { + mGutsListener = gutsListener; + } + public interface OnSettingsClickListener { public void onSettingsClick(String key); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index d59318e45e7e..3a37fb44b33a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -85,7 +85,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private ArrayList<MenuItem> mRightMenuItems; private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>(); private OnMenuEventListener mMenuListener; - private boolean mDismissRtl; private ValueAnimator mFadeAnimator; private boolean mAnimating; @@ -790,14 +789,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl return getParent().canViewBeDismissed(); } - @Override - public void setDismissRtl(boolean dismissRtl) { - mDismissRtl = dismissRtl; - if (mMenuContainer != null) { - createMenuViews(true); - } - } - public static class NotificationMenuItem implements MenuItem { View mMenuView; GutsContent mGutsContent; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt new file mode 100644 index 000000000000..31f4857e4b04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt @@ -0,0 +1,25 @@ +package com.android.systemui.statusbar.notification.stack + +import android.annotation.IntDef + +/** + * For now, declare the available notification buckets (sections) here so that other + * presentation code can decide what to do based on an entry's buckets + */ +@Retention(AnnotationRetention.SOURCE) +@IntDef( + prefix = ["BUCKET_"], + value = [ + BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, + BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT + ] +) +annotation class PriorityBucket + +const val BUCKET_UNKNOWN = 0 +const val BUCKET_MEDIA_CONTROLS = 1 +const val BUCKET_HEADS_UP = 2 +const val BUCKET_FOREGROUND_SERVICE = 3 +const val BUCKET_PEOPLE = 4 +const val BUCKET_ALERTING = 5 +const val BUCKET_SILENT = 6 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java index ab39de0f9bc7..bc172ce537f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 45ce20a1f08f..5f157a767c5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.ColorInt -import android.annotation.IntDef import android.annotation.LayoutRes import android.util.Log import android.view.LayoutInflater @@ -350,7 +349,7 @@ class NotificationSectionsManager @Inject internal constructor( silentHeaderView?.run { val hasActiveClearableNotifications = this@NotificationSectionsManager.parent .hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE) - setAreThereDismissableGentleNotifs(hasActiveClearableNotifications) + setClearSectionButtonEnabled(hasActiveClearableNotifications) } } @@ -448,25 +447,3 @@ class NotificationSectionsManager @Inject internal constructor( private const val DEBUG = false } } - -/** - * For now, declare the available notification buckets (sections) here so that other - * presentation code can decide what to do based on an entry's buckets - */ -@Retention(AnnotationRetention.SOURCE) -@IntDef( - prefix = ["BUCKET_"], - value = [ - BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, - BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT - ] -) -annotation class PriorityBucket - -const val BUCKET_UNKNOWN = 0 -const val BUCKET_MEDIA_CONTROLS = 1 -const val BUCKET_HEADS_UP = 2 -const val BUCKET_FOREGROUND_SERVICE = 3 -const val BUCKET_PEOPLE = 4 -const val BUCKET_ALERTING = 5 -const val BUCKET_SILENT = 6 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 58e44792571c..6a127102b726 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.Utils.shouldUseSplitNotificationShade; @@ -109,6 +109,7 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; import com.android.systemui.util.Assert; +import com.android.systemui.util.DumpUtilsKt; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -161,7 +162,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final Paint mBackgroundPaint = new Paint(); private final boolean mShouldDrawNotificationBackground; private boolean mHighPriorityBeforeSpeedBump; - private boolean mDismissRtl; private float mExpandedHeight; private int mOwnScrollY; @@ -613,16 +613,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable addView(mFgsSectionView, -1); } - void updateDismissRtlSetting(boolean dismissRtl) { - mDismissRtl = dismissRtl; - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - if (child instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl); - } - } - } - /** * Set the overexpansion of the panel to be applied to the view. */ @@ -2915,7 +2905,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateChronometerForChild(child); if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - row.setDismissRtl(mDismissRtl); row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX); } @@ -4911,54 +4900,42 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(String.format("[%s: pulsing=%s visibility=%s" - + " alpha=%f scrollY:%d maxTopPadding=%d showShelfOnly=%s" - + " qsExpandFraction=%f" - + " hideAmount=%f]", - this.getClass().getSimpleName(), - mPulsing ? "T" : "f", - getVisibility() == View.VISIBLE ? "visible" - : getVisibility() == View.GONE ? "gone" - : "invisible", - getAlpha(), - mAmbientState.getScrollY(), - mMaxTopPadding, - mShouldShowShelfOnly ? "T" : "f", - mQsExpansionFraction, - mAmbientState.getHideAmount())); - int childCount = getChildCount(); - pw.println(" Number of children: " + childCount); - pw.println(); + StringBuilder sb = new StringBuilder("[") + .append(this.getClass().getSimpleName()).append(":") + .append(" pulsing=").append(mPulsing ? "T" : "f") + .append(" visibility=").append(DumpUtilsKt.visibilityString(getVisibility())) + .append(" alpha=").append(getAlpha()) + .append(" scrollY=").append(mAmbientState.getScrollY()) + .append(" maxTopPadding=").append(mMaxTopPadding) + .append(" showShelfOnly=").append(mShouldShowShelfOnly ? "T" : "f") + .append(" qsExpandFraction=").append(mQsExpansionFraction) + .append(" isCurrentUserSetup=").append(mIsCurrentUserSetup) + .append(" hideAmount=").append(mAmbientState.getHideAmount()) + .append("]"); + pw.println(sb.toString()); + DumpUtilsKt.withIndenting(pw, ipw -> { + int childCount = getChildCount(); + ipw.println("Number of children: " + childCount); + ipw.println(); - for (int i = 0; i < childCount; i++) { - ExpandableView child = (ExpandableView) getChildAt(i); - child.dump(fd, pw, args); - if (!(child instanceof ExpandableNotificationRow)) { - pw.println(" " + child.getClass().getSimpleName()); - // Notifications dump it's viewstate as part of their dump to support children - ExpandableViewState viewState = child.getViewState(); - if (viewState == null) { - pw.println(" no viewState!!!"); - } else { - pw.print(" "); - viewState.dump(fd, pw, args); - pw.println(); - pw.println(); - } + for (int i = 0; i < childCount; i++) { + ExpandableView child = (ExpandableView) getChildAt(i); + child.dump(fd, ipw, args); + ipw.println(); + } + int transientViewCount = getTransientViewCount(); + pw.println("Transient Views: " + transientViewCount); + for (int i = 0; i < transientViewCount; i++) { + ExpandableView child = (ExpandableView) getTransientView(i); + child.dump(fd, pw, args); + } + View swipedView = mSwipeHelper.getSwipedView(); + pw.println("Swiped view: " + swipedView); + if (swipedView instanceof ExpandableView) { + ExpandableView expandableView = (ExpandableView) swipedView; + expandableView.dump(fd, pw, args); } - } - int transientViewCount = getTransientViewCount(); - pw.println(" Transient Views: " + transientViewCount); - for (int i = 0; i < transientViewCount; i++) { - ExpandableView child = (ExpandableView) getTransientView(i); - child.dump(fd, pw, args); - } - View swipedView = mSwipeHelper.getSwipedView(); - pw.println(" Swiped view: " + swipedView); - if (swipedView instanceof ExpandableView) { - ExpandableView expandableView = (ExpandableView) swipedView; - expandableView.dump(fd, pw, args); - } + }); } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 9923faeb8b5c..03fc76760133 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -783,9 +783,6 @@ public class NotificationStackScrollLayoutController { mTunerService.addTunable( (key, newValue) -> { switch (key) { - case Settings.Secure.NOTIFICATION_DISMISS_RTL: - mView.updateDismissRtlSetting("1".equals(newValue)); - break; case Settings.Secure.NOTIFICATION_HISTORY_ENABLED: updateFooter(); break; @@ -795,7 +792,6 @@ public class NotificationStackScrollLayoutController { } }, HIGH_PRIORITY, - Settings.Secure.NOTIFICATION_DISMISS_RTL, Settings.Secure.NOTIFICATION_HISTORY_ENABLED); mKeyguardMediaController.setVisibilityChangedListener(visible -> { @@ -810,14 +806,15 @@ public class NotificationStackScrollLayoutController { return Unit.INSTANCE; }); - // callback is invoked synchronously, updating mView immediately + // attach callback, and then call it to update mView immediately mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + mDeviceProvisionedListener.onDeviceProvisionedChanged(); if (mView.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener); - mSilentHeaderController.setOnClearAllClickListener(v -> clearSilentNotifications()); + mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications()); } private boolean isInVisibleLocation(NotificationEntry entry) { @@ -1304,6 +1301,9 @@ public class NotificationStackScrollLayoutController { } public void updateSectionBoundaries(String reason) { + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + return; + } mView.updateSectionBoundaries(reason); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java index 99ec7548fb9d..baf09c70f936 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java @@ -85,8 +85,12 @@ public class SectionHeaderView extends StackScrollerDecorView { return true; } - void setAreThereDismissableGentleNotifs(boolean areThereDismissableGentleNotifs) { - mClearAllButton.setVisibility(areThereDismissableGentleNotifs ? View.VISIBLE : View.GONE); + /** + * Show the clear section [X] button + * @param enabled + */ + public void setClearSectionButtonEnabled(boolean enabled) { + mClearAllButton.setVisibility(enabled ? View.VISIBLE : View.GONE); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 989f6b85668b..d5912e0ddbe7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -253,8 +253,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue state1 = adjustDisableFlags(state1); mCollapsedStatusBarFragmentLogger.logDisableFlagChange( - new DisableState(state1BeforeAdjustment, state2), - new DisableState(state1, state2)); + /* new= */ new DisableState(state1BeforeAdjustment, state2), + /* newAfterLocalModification= */ new DisableState(state1, state2)); final int old1 = mDisabled1; final int diff1 = state1 ^ old1; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt index 3c2b555eea68..4d472e47a387 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt @@ -28,22 +28,31 @@ class CollapsedStatusBarFragmentLogger @Inject constructor( private val disableFlagsLogger: DisableFlagsLogger, ) { - /** Logs a string representing the old and new disable flag states to [buffer]. */ + /** + * Logs a string representing the new state received by [CollapsedStatusBarFragment] and any + * modifications that were made to the flags locally. + * + * @param new see [DisableFlagsLogger.getDisableFlagsString] + * @param newAfterLocalModification see [DisableFlagsLogger.getDisableFlagsString] + */ fun logDisableFlagChange( - oldState: DisableFlagsLogger.DisableState, - newState: DisableFlagsLogger.DisableState) { + new: DisableFlagsLogger.DisableState, + newAfterLocalModification: DisableFlagsLogger.DisableState + ) { buffer.log( TAG, LogLevel.INFO, { - int1 = oldState.disable1 - int2 = oldState.disable2 - long1 = newState.disable1.toLong() - long2 = newState.disable2.toLong() + int1 = new.disable1 + int2 = new.disable2 + long1 = newAfterLocalModification.disable1.toLong() + long2 = newAfterLocalModification.disable2.toLong() }, { disableFlagsLogger.getDisableFlagsString( - DisableFlagsLogger.DisableState(int1, int2), + old = null, + new = DisableFlagsLogger.DisableState(int1, int2), + newAfterLocalModification = DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt()) ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index b9b663c33a45..353868ba969f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -45,6 +45,7 @@ import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.ListenerSet; import java.io.PrintWriter; import java.util.ArrayList; @@ -84,11 +85,11 @@ public class KeyguardBouncer { private final Runnable mRemoveViewRunnable = this::removeView; private final KeyguardBypassController mKeyguardBypassController; private KeyguardHostViewController mKeyguardViewController; - private final List<KeyguardResetCallback> mResetCallbacks = new ArrayList<>(); + private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>(); private final Runnable mResetRunnable = ()-> { if (mKeyguardViewController != null) { mKeyguardViewController.resetSecurityContainer(); - for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) { + for (KeyguardResetCallback callback : mResetCallbacks) { callback.onKeyguardReset(); } } @@ -602,7 +603,7 @@ public class KeyguardBouncer { } public void addKeyguardResetCallback(KeyguardResetCallback callback) { - mResetCallbacks.add(callback); + mResetCallbacks.addIfAbsent(callback); } public void removeKeyguardResetCallback(KeyguardResetCallback callback) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 6516abd143ed..fbe59a2743b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -6,6 +6,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; +import android.os.Trace; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; @@ -336,12 +337,14 @@ public class NotificationIconAreaController implements } private void updateNotificationIcons() { + Trace.beginSection("NotificationIconAreaController.updateNotificationIcons"); updateStatusBarIcons(); updateShelfIcons(); updateCenterIcon(); updateAodNotificationIcons(); applyNotificationIconsTint(); + Trace.endSection(); } private void updateShelfIcons() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 3fe393d99c7f..f6005a834661 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -22,6 +22,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.END; import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; import static androidx.constraintlayout.widget.ConstraintSet.START; import static androidx.constraintlayout.widget.ConstraintSet.TOP; +import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; @@ -116,6 +117,7 @@ import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeLog; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.fragments.FragmentService; import com.android.systemui.media.KeyguardMediaController; @@ -169,6 +171,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -419,6 +422,9 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mIsFullWidth; private boolean mBlockingExpansionForCurrentTouch; + // TODO (b/204204226): no longer needed once refactor is complete + private final boolean mUseCombinedQSHeaders; + /** * Following variables maintain state of events when input focus transfer may occur. */ @@ -686,8 +692,10 @@ public class NotificationPanelViewController extends PanelViewController { SplitShadeHeaderController splitShadeHeaderController, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, LockscreenGestureLogger lockscreenGestureLogger, + PanelExpansionStateManager panelExpansionStateManager, NotificationRemoteInputManager remoteInputManager, - ControlsComponent controlsComponent) { + ControlsComponent controlsComponent, + FeatureFlags featureFlags) { super(view, falsingManager, dozeLog, @@ -699,6 +707,7 @@ public class NotificationPanelViewController extends PanelViewController { flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager, lockscreenGestureLogger, + panelExpansionStateManager, ambientState); mView = view; mVibratorHelper = vibratorHelper; @@ -802,6 +811,8 @@ public class NotificationPanelViewController extends PanelViewController { mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count); updateUserSwitcherFlags(); onFinishInflate(); + + mUseCombinedQSHeaders = featureFlags.useCombinedQSHeaders(); } private void onFinishInflate() { @@ -1001,6 +1012,9 @@ public class NotificationPanelViewController extends PanelViewController { } else { constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END); constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START); + if (mUseCombinedQSHeaders) { + constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT); + } } constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth; constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth; @@ -2205,10 +2219,6 @@ public class NotificationPanelViewController extends PanelViewController { mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */, false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */); } - for (int i = 0; i < mExpansionListeners.size(); i++) { - mExpansionListeners.get(i).onQsExpansionChanged( - mQsMaxExpansionHeight != 0 ? mQsExpansionHeight / mQsMaxExpansionHeight : 0); - } if (DEBUG) { mView.invalidate(); } @@ -2219,6 +2229,7 @@ public class NotificationPanelViewController extends PanelViewController { float qsExpansionFraction = computeQsExpansionFraction(); mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation(), mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()); + mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 36bd31b2efe3..09bcb69790f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -54,6 +54,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.tuner.TunerService; @@ -105,7 +106,8 @@ public class NotificationShadeWindowViewController { private boolean mExpandingBelowNotch; private final DockManager mDockManager; private final NotificationPanelViewController mNotificationPanelViewController; - private final StatusBarWindowView mStatusBarWindowView; + private final PanelExpansionStateManager mPanelExpansionStateManager; + private final StatusBarWindowController mStatusBarWindowController; // Used for determining view / touch intersection private int[] mTempLocation = new int[2]; @@ -134,7 +136,8 @@ public class NotificationShadeWindowViewController { NotificationShadeDepthController depthController, NotificationShadeWindowView notificationShadeWindowView, NotificationPanelViewController notificationPanelViewController, - StatusBarWindowView statusBarWindowView, + PanelExpansionStateManager panelExpansionStateManager, + StatusBarWindowController statusBarWindowController, NotificationStackScrollLayoutController notificationStackScrollLayoutController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, LockIconViewController lockIconViewController) { @@ -157,8 +160,9 @@ public class NotificationShadeWindowViewController { mShadeController = shadeController; mDockManager = dockManager; mNotificationPanelViewController = notificationPanelViewController; + mPanelExpansionStateManager = panelExpansionStateManager; mDepthController = depthController; - mStatusBarWindowView = statusBarWindowView; + mStatusBarWindowController = statusBarWindowController; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLockIconViewController = lockIconViewController; @@ -442,7 +446,7 @@ public class NotificationShadeWindowViewController { setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper()); mDepthController.setRoot(mView); - mNotificationPanelViewController.addExpansionListener(mDepthController); + mPanelExpansionStateManager.addListener(mDepthController); } public NotificationShadeWindowView getView() { @@ -496,7 +500,7 @@ public class NotificationShadeWindowViewController { if (statusBarView != null) { mBarTransitions = new PhoneStatusBarTransitions( statusBarView, - mStatusBarWindowView.findViewById(R.id.status_bar_container)); + mStatusBarWindowController.getBackgroundView()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index e5296af86b81..b508ddfdafba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -59,12 +59,12 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wm.shell.animation.FlingAnimationUtils; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; public abstract class PanelViewController { public static final boolean DEBUG = PanelBar.DEBUG; @@ -86,7 +86,6 @@ public abstract class PanelViewController { private boolean mVibrateOnOpening; protected boolean mIsLaunchAnimationRunning; private int mFixedDuration = NO_FIXED_DURATION; - protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>(); protected float mOverExpansion; /** @@ -185,6 +184,7 @@ public abstract class PanelViewController { protected final SysuiStatusBarStateController mStatusBarStateController; protected final AmbientState mAmbientState; protected final LockscreenGestureLogger mLockscreenGestureLogger; + private final PanelExpansionStateManager mPanelExpansionStateManager; private final TouchHandler mTouchHandler; protected abstract void onExpandingFinished(); @@ -211,20 +211,25 @@ public abstract class PanelViewController { return mAmbientState; } - public PanelViewController(PanelView view, - FalsingManager falsingManager, DozeLog dozeLog, + public PanelViewController( + PanelView view, + FalsingManager falsingManager, + DozeLog dozeLog, KeyguardStateController keyguardStateController, - SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper, + SysuiStatusBarStateController statusBarStateController, + VibratorHelper vibratorHelper, StatusBarKeyguardViewManager statusBarKeyguardViewManager, LatencyTracker latencyTracker, FlingAnimationUtils.Builder flingAnimationUtilsBuilder, StatusBarTouchableRegionManager statusBarTouchableRegionManager, LockscreenGestureLogger lockscreenGestureLogger, + PanelExpansionStateManager panelExpansionStateManager, AmbientState ambientState) { mAmbientState = ambientState; mView = view; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLockscreenGestureLogger = lockscreenGestureLogger; + mPanelExpansionStateManager = panelExpansionStateManager; mTouchHandler = createTouchHandler(); mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override @@ -1088,9 +1093,8 @@ public abstract class PanelViewController { mBar.panelExpansionChanged(mExpandedFraction, isExpanded()); } updateVisibility(); - for (int i = 0; i < mExpansionListeners.size(); i++) { - mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking); - } + mPanelExpansionStateManager.onPanelExpansionChanged( + mExpandedFraction, isExpanded(), mTracking); } public boolean isExpanded() { @@ -1102,10 +1106,6 @@ public abstract class PanelViewController { && !mIsSpringBackAnimation; } - public void addExpansionListener(PanelExpansionListener panelExpansionListener) { - mExpansionListeners.add(panelExpansionListener); - } - protected abstract boolean isPanelVisibleBecauseOfHeadsUp(); /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 883313bdc096..b7686989459b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -42,7 +42,6 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.util.leak.RotationUtils; -import java.util.List; import java.util.Objects; public class PhoneStatusBarView extends PanelBar { @@ -65,8 +64,6 @@ public class PhoneStatusBarView extends PanelBar { private DisplayCutout mDisplayCutout; private int mStatusBarHeight; @Nullable - private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners; - @Nullable private TouchEventHandler mTouchEventHandler; /** @@ -83,11 +80,6 @@ public class PhoneStatusBarView extends PanelBar { mBar = bar; } - public void setExpansionChangedListeners( - @Nullable List<StatusBar.ExpansionChangedListener> listeners) { - mExpansionChangedListeners = listeners; - } - void setTouchEventHandler(TouchEventHandler handler) { mTouchEventHandler = handler; } @@ -203,16 +195,6 @@ public class PhoneStatusBarView extends PanelBar { return super.onInterceptTouchEvent(event); } - @Override - public void panelExpansionChanged(float frac, boolean expanded) { - super.panelExpansionChanged(frac, expanded); - if (mExpansionChangedListeners != null) { - for (StatusBar.ExpansionChangedListener listener : mExpansionChangedListeners) { - listener.onExpansionChanged(frac, expanded); - } - } - } - public void updateResources() { mCutoutSideNudge = getResources().getDimensionPixelSize( R.dimen.display_cutout_margin_consumption); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 1921357ddf7c..cef0613c3f9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -53,6 +53,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.notification.stack.ViewState; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.AlarmTimeout; @@ -233,7 +234,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, ConfigurationController configurationController, @Main Executor mainExecutor, - UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, + PanelExpansionStateManager panelExpansionStateManager) { mScrimStateListener = lightBarController::setScrimState; mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; @@ -269,6 +271,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump ScrimController.this.onThemeChanged(); } }); + panelExpansionStateManager.addListener( + (fraction, expanded, tracking) -> setRawPanelExpansionFraction(fraction) + ); mColors = new GradientColors(); } @@ -481,11 +486,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump * * The expansion fraction is tied to the scrim opacity. * - * See {@link PanelBar#panelExpansionChanged}. + * See {@link PanelExpansionListener#onPanelExpansionChanged}. * * @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded. */ - public void setRawPanelExpansionFraction( + @VisibleForTesting + void setRawPanelExpansionFraction( @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) { if (isNaN(rawPanelExpansionFraction)) { throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt index 873289130139..bac4234389aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone import android.view.View +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.R import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView @@ -37,6 +39,7 @@ class SplitShadeHeaderController @Inject constructor( batteryMeterViewController: BatteryMeterViewController ) { + private val combinedHeaders = featureFlags.useCombinedQSHeaders() // TODO(b/194178072) Handle RSSI hiding when multi carrier private val iconManager: StatusBarIconController.IconManager private val qsCarrierGroupController: QSCarrierGroupController @@ -75,6 +78,19 @@ class SplitShadeHeaderController @Inject constructor( } } + var qsExpandedFraction = -1f + set(value) { + if (visible && field != value) { + field = value + updateVisibility() + } + } + + private val constraintSplit = ConstraintSet() + .apply { load(statusBar.context, R.xml.split_header) } + private val constraintQQS = ConstraintSet().apply { load(statusBar.context, R.xml.qqs_header) } + private val constraintQS = ConstraintSet().apply { load(statusBar.context, R.xml.qs_header) } + init { batteryMeterViewController.init() val batteryIcon: BatteryMeterView = statusBar.findViewById(R.id.batteryRemainingIcon) @@ -91,7 +107,7 @@ class SplitShadeHeaderController @Inject constructor( } private fun updateVisibility() { - val visibility = if (!splitShadeMode) { + val visibility = if (!splitShadeMode && !combinedHeaders) { View.GONE } else if (shadeExpanded) { View.VISIBLE @@ -102,6 +118,21 @@ class SplitShadeHeaderController @Inject constructor( statusBar.visibility = visibility visible = visibility == View.VISIBLE } + updateConstraints() + } + + private fun updateConstraints() { + if (!combinedHeaders) { + return + } + statusBar as ConstraintLayout + if (splitShadeMode) { + constraintSplit.applyTo(statusBar) + } else if (qsExpandedFraction == 1f) { + constraintQS.applyTo(statusBar) + } else { + constraintQQS.applyTo(statusBar) + } } private fun updateListeners() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index bc50893d9a64..c25304e1efb3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -129,7 +129,6 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.AutoReinflateContainer; import com.android.systemui.DejankUtils; -import com.android.systemui.Dumpable; import com.android.systemui.EventLogTags; import com.android.systemui.InitController; import com.android.systemui.Prefs; @@ -137,7 +136,6 @@ import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DelegateLaunchAnimatorController; -import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.biometrics.AuthRippleController; @@ -174,7 +172,7 @@ import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanelController; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.scrim.ScrimView; -import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.BackDropView; @@ -207,6 +205,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; @@ -219,6 +218,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -234,6 +234,7 @@ import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; import com.android.systemui.unfold.UnfoldTransitionWallpaperController; import com.android.systemui.unfold.config.UnfoldTransitionConfig; import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; +import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.MessageRouter; @@ -247,7 +248,6 @@ import com.android.wm.shell.startingsurface.StartingSurface; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -439,10 +439,6 @@ public class StatusBar extends SystemUI implements mCommandQueueCallbacks.animateCollapsePanels(flags, force); } - public interface ExpansionChangedListener { - void onExpansionChanged(float expansion, boolean expanded); - } - /** * The {@link StatusBarState} of the status bar. */ @@ -503,7 +499,6 @@ public class StatusBar extends SystemUI implements private final StatusBarNotificationActivityStarter.Builder mStatusBarNotificationActivityStarterBuilder; private final ShadeController mShadeController; - private final StatusBarWindowView mStatusBarWindowView; private final LightsOutNotifController mLightsOutNotifController; private final InitController mInitController; @@ -535,13 +530,15 @@ public class StatusBar extends SystemUI implements private final int[] mAbsPos = new int[2]; + private final NotifShadeEventSource mNotifShadeEventSource; protected final NotificationEntryManager mEntryManager; private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; private final NotificationViewHierarchyManager mViewHierarchyManager; + private final PanelExpansionStateManager mPanelExpansionStateManager; private final KeyguardViewMediator mKeyguardViewMediator; protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; - private final BrightnessSlider.Factory mBrightnessSliderFactory; + private final BrightnessSliderController.Factory mBrightnessSliderFactory; private final FeatureFlags mFeatureFlags; private final UnfoldTransitionConfig mUnfoldTransitionConfig; private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation; @@ -554,8 +551,6 @@ public class StatusBar extends SystemUI implements private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private final TunerService mTunerService; - private final List<ExpansionChangedListener> mExpansionChangedListeners; - // Flags for disabling the status bar // Two variables becaseu the first one evidently ran out of room for new flags. private int mDisabled1 = 0; @@ -677,7 +672,6 @@ public class StatusBar extends SystemUI implements private HeadsUpAppearanceController mHeadsUpAppearanceController; private final ActivityLaunchAnimator mActivityLaunchAnimator; - private final DialogLaunchAnimator mDialogLaunchAnimator; private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider; protected StatusBarNotificationPresenter mPresenter; private NotificationActivityStarter mNotificationActivityStarter; @@ -718,11 +712,13 @@ public class StatusBar extends SystemUI implements FalsingManager falsingManager, FalsingCollector falsingCollector, BroadcastDispatcher broadcastDispatcher, + NotifShadeEventSource notifShadeEventSource, NotificationEntryManager notificationEntryManager, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, + PanelExpansionStateManager panelExpansionStateManager, KeyguardViewMediator keyguardViewMediator, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @@ -764,7 +760,6 @@ public class StatusBar extends SystemUI implements StatusBarNotificationActivityStarter.Builder statusBarNotificationActivityStarterBuilder, ShadeController shadeController, - StatusBarWindowView statusBarWindowView, StatusBarKeyguardViewManager statusBarKeyguardViewManager, ViewMediatorCallback viewMediatorCallback, InitController initController, @@ -781,7 +776,7 @@ public class StatusBar extends SystemUI implements Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, StatusBarTouchableRegionManager statusBarTouchableRegionManager, NotificationIconAreaController notificationIconAreaController, - BrightnessSlider.Factory brightnessSliderFactory, + BrightnessSliderController.Factory brightnessSliderFactory, UnfoldTransitionConfig unfoldTransitionConfig, Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation, Lazy<UnfoldTransitionWallpaperController> unfoldTransitionWallpaperController, @@ -802,8 +797,7 @@ public class StatusBar extends SystemUI implements Optional<StartingSurface> startingSurfaceOptional, TunerService tunerService, DumpManager dumpManager, - ActivityLaunchAnimator activityLaunchAnimator, - DialogLaunchAnimator dialogLaunchAnimator) { + ActivityLaunchAnimator activityLaunchAnimator) { super(context); mNotificationsController = notificationsController; mLightBarController = lightBarController; @@ -823,11 +817,13 @@ public class StatusBar extends SystemUI implements mFalsingCollector = falsingCollector; mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; + mNotifShadeEventSource = notifShadeEventSource; mEntryManager = notificationEntryManager; mGutsManager = notificationGutsManager; mNotificationLogger = notificationLogger; mNotificationInterruptStateProvider = notificationInterruptStateProvider; mViewHierarchyManager = notificationViewHierarchyManager; + mPanelExpansionStateManager = panelExpansionStateManager; mKeyguardViewMediator = keyguardViewMediator; mDisplayMetrics = displayMetrics; mMetricsLogger = metricsLogger; @@ -868,7 +864,6 @@ public class StatusBar extends SystemUI implements mSplitScreenOptional = splitScreenOptional; mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder; mShadeController = shadeController; - mStatusBarWindowView = statusBarWindowView; mLightsOutNotifController = lightsOutNotifController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardViewMediatorCallback = viewMediatorCallback; @@ -903,10 +898,7 @@ public class StatusBar extends SystemUI implements mStartingSurfaceOptional = startingSurfaceOptional; lockscreenShadeTransitionController.setStatusbar(this); - mExpansionChangedListeners = new ArrayList<>(); - addExpansionChangedListener( - (expansion, expanded) -> mScrimController.setRawPanelExpansionFraction(expansion)); - addExpansionChangedListener(this::onPanelExpansionChanged); + mPanelExpansionStateManager.addListener(this::onPanelExpansionChanged); mBubbleExpandListener = (isExpanding, key) -> mContext.getMainExecutor().execute(() -> { @@ -916,7 +908,6 @@ public class StatusBar extends SystemUI implements mActivityIntentHelper = new ActivityIntentHelper(mContext); mActivityLaunchAnimator = activityLaunchAnimator; - mDialogLaunchAnimator = dialogLaunchAnimator; // The status bar background may need updating when the ongoing call status changes. mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode()); @@ -932,8 +923,6 @@ public class StatusBar extends SystemUI implements data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel)); mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT, id -> onLaunchTransitionTimeout()); - - dumpManager.registerDumpable(this); } @Override @@ -1156,16 +1145,14 @@ public class StatusBar extends SystemUI implements mNotificationLogger.setUpWithContainer(notifListContainer); mNotificationIconAreaController.setupShelf(mNotificationShelfController); - mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator); - mNotificationPanelViewController.addExpansionListener( - this::dispatchPanelExpansionForKeyguardDismiss); + mPanelExpansionStateManager.addListener(mWakeUpCoordinator); mUserSwitcherController.init(mNotificationShadeWindowView); // Allow plugins to reference DarkIconDispatcher and StatusBarStateController mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class); mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class); - FragmentHostManager.get(mStatusBarWindowView) + mStatusBarWindowController.getFragmentHostManager() .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment; @@ -1176,10 +1163,6 @@ public class StatusBar extends SystemUI implements mStatusBarView.setPanelStateChangeListener( mNotificationPanelViewController.getPanelStateChangeListener()); mStatusBarView.setScrimController(mScrimController); - mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners); - for (ExpansionChangedListener listener : mExpansionChangedListeners) { - sendInitialExpansionAmount(listener); - } mNotificationPanelViewController.setBar(mStatusBarView); @@ -1408,12 +1391,6 @@ public class StatusBar extends SystemUI implements mDeviceProvisionedController.addCallback(mUserSetupObserver); mUserSetupObserver.onUserSetupChanged(); - for (ExpansionChangedListener listener : mExpansionChangedListeners) { - // The initial expansion amount comes from mNotificationPanelViewController, so we - // should send the amount once we've fully set up that controller. - sendInitialExpansionAmount(listener); - } - // disable profiling bars, since they overlap and clutter the output on app windows ThreadedRenderer.overrideProperty("disableProfileBars", "true"); @@ -1423,15 +1400,15 @@ public class StatusBar extends SystemUI implements /** - * When swiping up to dismiss the lock screen, the panel expansion goes from 1f to 0f. This - * results in the clock/notifications/other content disappearing off the top of the screen. + * When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f. + * This results in the clock/notifications/other content disappearing off the top of the screen. * - * We also use the expansion amount to animate in the app/launcher surface from the bottom of + * We also use the expansion fraction to animate in the app/launcher surface from the bottom of * the screen, 'pushing' off the notifications and other content. To do this, we dispatch the - * expansion amount to the KeyguardViewMediator if we're in the process of dismissing the + * expansion fraction to the KeyguardViewMediator if we're in the process of dismissing the * keyguard. */ - private void dispatchPanelExpansionForKeyguardDismiss(float expansion, boolean trackingTouch) { + private void dispatchPanelExpansionForKeyguardDismiss(float fraction, boolean trackingTouch) { // Things that mean we're not dismissing the keyguard, and should ignore this expansion: // - Keyguard isn't even visible. // - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt). @@ -1450,12 +1427,14 @@ public class StatusBar extends SystemUI implements || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe() || mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { mKeyguardStateController.notifyKeyguardDismissAmountChanged( - 1f - expansion, trackingTouch); + 1f - fraction, trackingTouch); } } - private void onPanelExpansionChanged(float frac, boolean expanded) { - if (frac == 0 || frac == 1) { + private void onPanelExpansionChanged(float fraction, boolean expanded, boolean tracking) { + dispatchPanelExpansionForKeyguardDismiss(fraction, tracking); + + if (fraction == 0 || fraction == 1) { if (getNavigationBarView() != null) { getNavigationBarView().onStatusBarPanelStateChanged(); } @@ -1506,6 +1485,7 @@ public class StatusBar extends SystemUI implements mDynamicPrivacyController, mKeyguardStateController, mKeyguardIndicationController, + mFeatureFlags, this /* statusBar */, mShadeController, mLockscreenShadeTransitionController, @@ -1513,6 +1493,7 @@ public class StatusBar extends SystemUI implements mViewHierarchyManager, mLockscreenUserManager, mStatusBarStateController, + mNotifShadeEventSource, mEntryManager, mMediaManager, mGutsManager, @@ -1665,8 +1646,11 @@ public class StatusBar extends SystemUI implements }); mStatusBarKeyguardViewManager.registerStatusBar( /* statusBar= */ this, - mNotificationPanelViewController, mBiometricUnlockController, - mStackScroller, mKeyguardBypassController); + mNotificationPanelViewController, + mPanelExpansionStateManager, + mBiometricUnlockController, + mStackScroller, + mKeyguardBypassController); mKeyguardIndicationController .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); @@ -1687,10 +1671,6 @@ public class StatusBar extends SystemUI implements return mNotificationShadeWindowView; } - public StatusBarWindowView getStatusBarWindow() { - return mStatusBarWindowView; - } - public NotificationShadeWindowViewController getNotificationShadeWindowViewController() { return mNotificationShadeWindowViewController; } @@ -2417,7 +2397,7 @@ public class StatusBar extends SystemUI implements pw.print(" mDozing="); pw.println(mDozing); pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported); - pw.println(" StatusBarWindowView: "); + pw.println(" ShadeWindowView: "); if (mNotificationShadeWindowViewController != null) { mNotificationShadeWindowViewController.dump(fd, pw, args); dumpBarTransitions(pw, "PhoneStatusBarTransitions", @@ -2439,8 +2419,14 @@ public class StatusBar extends SystemUI implements } pw.println(" mStackScroller: "); if (mStackScroller != null) { - pw.print (" "); - ((Dumpable) mStackScroller).dump(fd, pw, args); + DumpUtilsKt.withIndenting(pw, ipw -> { + // Triple indent until we rewrite the rest of this dump() + ipw.increaseIndent(); + ipw.increaseIndent(); + mStackScroller.dump(fd, ipw, args); + ipw.decreaseIndent(); + ipw.decreaseIndent(); + }); } pw.println(" Theme:"); String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + ""; @@ -2648,26 +2634,12 @@ public class StatusBar extends SystemUI implements private ActivityLaunchAnimator.Controller wrapAnimationController( ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { View rootView = animationController.getLaunchContainer().getRootView(); - if (rootView == mStatusBarWindowView) { - // We are animating a view in the status bar. We have to make sure that the status bar - // window matches the full screen during the animation and that we are expanding the - // view below the other status bar text. - animationController.setLaunchContainer( - mStatusBarWindowController.getLaunchAnimationContainer()); - - return new DelegateLaunchAnimatorController(animationController) { - @Override - public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { - getDelegate().onLaunchAnimationStart(isExpandingFullyAbove); - mStatusBarWindowController.setLaunchAnimationRunning(true); - } - @Override - public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) { - getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove); - mStatusBarWindowController.setLaunchAnimationRunning(false); - } - }; + Optional<ActivityLaunchAnimator.Controller> controllerFromStatusBar = + mStatusBarWindowController.wrapAnimationControllerIfInStatusBar( + rootView, animationController); + if (controllerFromStatusBar.isPresent()) { + return controllerFromStatusBar.get(); } if (dismissShade && rootView == mNotificationShadeWindowView) { @@ -4257,24 +4229,6 @@ public class StatusBar extends SystemUI implements return mTransientShown; } - - public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) { - mExpansionChangedListeners.add(listener); - sendInitialExpansionAmount(listener); - } - - private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) { - if (mNotificationPanelViewController != null) { - expansionChangedListener.onExpansionChanged( - mNotificationPanelViewController.getExpandedFraction(), - mNotificationPanelViewController.isExpanded()); - } - } - - public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) { - mExpansionChangedListeners.remove(listener); - } - private void updateLightRevealScrimVisibility() { if (mLightRevealScrim == null) { // status bar may not be inflated yet @@ -4486,8 +4440,6 @@ public class StatusBar extends SystemUI implements && !mBiometricUnlockController.isWakeAndUnlock()) { mLightRevealScrim.setRevealAmount(1f - linear); } - - mDialogLaunchAnimator.onDozeAmountChanged(linear); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index cac66a3186ca..30e668ac4431 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -63,6 +63,8 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -266,6 +268,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void registerStatusBar(StatusBar statusBar, NotificationPanelViewController notificationPanelViewController, + PanelExpansionStateManager panelExpansionStateManager, BiometricUnlockController biometricUnlockController, View notificationContainer, KeyguardBypassController bypassController) { @@ -275,7 +278,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb ViewGroup container = mStatusBar.getBouncerContainer(); mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback); mNotificationPanelViewController = notificationPanelViewController; - notificationPanelViewController.addExpansionListener(this); + if (panelExpansionStateManager != null) { + panelExpansionStateManager.addListener(this); + } mBypassController = bypassController; mNotificationContainer = notificationContainer; mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create( @@ -323,7 +328,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } @Override - public void onPanelExpansionChanged(float expansion, boolean tracking) { + public void onPanelExpansionChanged(float fraction, boolean expanded, boolean tracking) { // We don't want to translate the bounce when: // • Keyguard is occluded, because we're in a FLAG_SHOW_WHEN_LOCKED activity and need to // conserve the original animation. @@ -336,14 +341,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); } else if (mShowing) { if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) { - mBouncer.setExpansion(expansion); + mBouncer.setExpansion(fraction); } - if (expansion != KeyguardBouncer.EXPANSION_HIDDEN && tracking + if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking && !mKeyguardStateController.canDismissLockScreen() && !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) { mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */); } - } else if (mPulsing && expansion == KeyguardBouncer.EXPANSION_VISIBLE) { + } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) { // Panel expanded while pulsing but didn't translate the bouncer (because we are // unlocked.) Let's simply wake-up to dismiss the lock screen. mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), mStatusBar.getBouncerContainer(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index c655964e64bc..ecd5c985154c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -17,9 +17,7 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED; import static com.android.systemui.statusbar.phone.StatusBar.DEBUG; import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG; -import static com.android.systemui.statusbar.phone.StatusBar.SPEW; -import android.annotation.Nullable; import android.app.KeyguardManager; import android.content.Context; import android.os.RemoteException; @@ -36,7 +34,6 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.MessagingGroup; import com.android.internal.widget.MessagingMessage; import com.android.keyguard.KeyguardUpdateMonitor; @@ -44,6 +41,7 @@ import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceNotificationListener; import com.android.systemui.InitController; import com.android.systemui.R; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.statusbar.CommandQueue; @@ -59,10 +57,10 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -88,6 +86,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, private final NotificationViewHierarchyManager mViewHierarchyManager; private final NotificationLockscreenUserManager mLockscreenUserManager; private final SysuiStatusBarStateController mStatusBarStateController; + private final NotifShadeEventSource mNotifShadeEventSource; private final NotificationEntryManager mEntryManager; private final NotificationMediaManager mMediaManager; private final NotificationGutsManager mGutsManager; @@ -100,6 +99,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, private final DozeScrimController mDozeScrimController; private final ScrimController mScrimController; private final KeyguardIndicationController mKeyguardIndicationController; + private final FeatureFlags mFeatureFlags; private final StatusBar mStatusBar; private final ShadeController mShadeController; private final LockscreenShadeTransitionController mShadeTransitionController; @@ -127,6 +127,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, + FeatureFlags featureFlags, StatusBar statusBar, ShadeController shadeController, LockscreenShadeTransitionController shadeTransitionController, @@ -134,6 +135,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, NotificationViewHierarchyManager notificationViewHierarchyManager, NotificationLockscreenUserManager lockscreenUserManager, SysuiStatusBarStateController sysuiStatusBarStateController, + NotifShadeEventSource notifShadeEventSource, NotificationEntryManager notificationEntryManager, NotificationMediaManager notificationMediaManager, NotificationGutsManager notificationGutsManager, @@ -148,6 +150,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mHeadsUpManager = headsUp; mDynamicPrivacyController = dynamicPrivacyController; mKeyguardIndicationController = keyguardIndicationController; + mFeatureFlags = featureFlags; // TODO: use KeyguardStateController#isOccluded to remove this dependency mStatusBar = statusBar; mShadeController = shadeController; @@ -156,6 +159,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mViewHierarchyManager = notificationViewHierarchyManager; mLockscreenUserManager = lockscreenUserManager; mStatusBarStateController = sysuiStatusBarStateController; + mNotifShadeEventSource = notifShadeEventSource; mEntryManager = notificationEntryManager; mMediaManager = notificationMediaManager; mGutsManager = notificationGutsManager; @@ -186,30 +190,18 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mNotificationPanel.createRemoteInputDelegate()); initController.addPostInitTask(() -> { - NotificationEntryListener notificationEntryListener = new NotificationEntryListener() { - @Override - public void onEntryRemoved( - @Nullable NotificationEntry entry, - NotificationVisibility visibility, - boolean removedByUser, - int reason) { - StatusBarNotificationPresenter.this.onNotificationRemoved( - entry.getKey(), entry.getSbn(), reason); - if (removedByUser) { - maybeEndAmbientPulse(); - } - } - }; - mKeyguardIndicationController.init(); mViewHierarchyManager.setUpWithPresenter(this, stackScrollerController.getNotificationListContainer()); - mEntryManager.setUpWithPresenter(this); - mEntryManager.addNotificationEntryListener(notificationEntryListener); - mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager); - mEntryManager.addNotificationLifetimeExtender(mGutsManager); - mEntryManager.addNotificationLifetimeExtenders( - remoteInputManager.getLifetimeExtenders()); + mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied); + mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse); + if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + mEntryManager.setUpWithPresenter(this); + mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager); + mEntryManager.addNotificationLifetimeExtender(mGutsManager); + mEntryManager.addNotificationLifetimeExtenders( + remoteInputManager.getLifetimeExtenders()); + } notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor); mLockscreenUserManager.setUpWithPresenter(this); mMediaManager.setUpWithPresenter(this); @@ -226,8 +218,21 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, configurationController.addCallback(this); } + /** Called when the shade has been emptied to attempt to close the shade */ + private void maybeClosePanelForShadeEmptied() { + if (CLOSE_PANEL_WHEN_EMPTIED + && !mNotificationPanel.isTracking() + && !mNotificationPanel.isQsExpanded() + && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED + && !isCollapsing()) { + mStatusBarStateController.setState(StatusBarState.KEYGUARD); + } + } + @Override public void onDensityOrFontScaleChanged() { + // TODO(b/145659174): Remove legacy pipeline code + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return; MessagingMessage.dropCache(); MessagingGroup.dropCache(); if (!mKeyguardUpdateMonitor.isSwitchingUser()) { @@ -239,8 +244,10 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, @Override public void onUiModeChanged() { + // TODO(b/145659174): Remove legacy pipeline code + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return; if (!mKeyguardUpdateMonitor.isSwitchingUser()) { - updateNotificationOnUiModeChanged(); + updateNotificationsOnUiModeChanged(); } else { mDispatchUiModeChangeOnUserSwitched = true; } @@ -251,7 +258,9 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, onDensityOrFontScaleChanged(); } - private void updateNotificationOnUiModeChanged() { + private void updateNotificationsOnUiModeChanged() { + // TODO(b/145659174): Remove legacy pipeline code + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return; List<NotificationEntry> userNotifications = mEntryManager.getActiveNotificationsForCurrentUser(); for (int i = 0; i < userNotifications.size(); i++) { @@ -264,6 +273,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } private void updateNotificationsOnDensityOrFontScaleChanged() { + // TODO(b/145659174): Remove legacy pipeline code + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return; List<NotificationEntry> userNotifications = mEntryManager.getActiveNotificationsForCurrentUser(); for (int i = 0; i < userNotifications.size(); i++) { @@ -276,6 +287,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } } + @Override public boolean isCollapsing() { return mNotificationPanel.isCollapsing() @@ -302,27 +314,10 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason)); return; } - mViewHierarchyManager.updateNotificationViews(); - mNotificationPanel.updateNotificationViews(reason); } - private void onNotificationRemoved(String key, StatusBarNotification old, int reason) { - if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); - - if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() - && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded() - && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED - && !isCollapsing()) { - mStatusBarStateController.setState(StatusBarState.KEYGUARD); - } - } - - public boolean hasActiveNotifications() { - return mEntryManager.hasActiveNotifications(); - } - @Override public void onUserSwitched(int newUserId) { // Begin old BaseStatusBar.userSwitched @@ -335,7 +330,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mReinflateNotificationsOnUserSwitched = false; } if (mDispatchUiModeChangeOnUserSwitched) { - updateNotificationOnUiModeChanged(); + updateNotificationsOnUiModeChanged(); mDispatchUiModeChangeOnUserSwitched = false; } updateNotificationViews("user switched"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index 9d2dbc12c97d..f8895d338e13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -36,14 +36,21 @@ import android.os.RemoteException; import android.util.Log; import android.view.Gravity; import android.view.IWindowManager; +import android.view.LayoutInflater; import android.view.Surface; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.DelegateLaunchAnimatorController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.fragments.FragmentHostManager; + +import java.util.Optional; import javax.inject.Inject; @@ -63,7 +70,9 @@ public class StatusBarWindowController { private int mBarHeight = -1; private final State mCurrentState = new State(); - private final ViewGroup mStatusBarView; + private final ViewGroup mStatusBarWindowView; + // The container in which we should run launch animations started from the status bar and + // expanding into the opening window. private final ViewGroup mLaunchAnimationContainer; private WindowManager.LayoutParams mLp; private final WindowManager.LayoutParams mLpChanged; @@ -73,15 +82,14 @@ public class StatusBarWindowController { Context context, WindowManager windowManager, IWindowManager iWindowManager, - StatusBarWindowView statusBarWindowView, StatusBarContentInsetsProvider contentInsetsProvider, @Main Resources resources) { mContext = context; mWindowManager = windowManager; mIWindowManager = iWindowManager; mContentInsetsProvider = contentInsetsProvider; - mStatusBarView = statusBarWindowView; - mLaunchAnimationContainer = mStatusBarView.findViewById( + mStatusBarWindowView = createWindowView(mContext); + mLaunchAnimationContainer = mStatusBarWindowView.findViewById( R.id.status_bar_launch_animation_container); mLpChanged = new WindowManager.LayoutParams(); mResources = resources; @@ -119,13 +127,63 @@ public class StatusBarWindowController { // hardware-accelerated. mLp = getBarLayoutParams(mContext.getDisplay().getRotation()); - mWindowManager.addView(mStatusBarView, mLp); + mWindowManager.addView(mStatusBarWindowView, mLp); mLpChanged.copyFrom(mLp); mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations); calculateStatusBarLocationsForAllRotations(); } + /** Adds the given view to the status bar window view. */ + public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) { + mStatusBarWindowView.addView(view, layoutParams); + } + + /** Returns the status bar window's background view. */ + public View getBackgroundView() { + return mStatusBarWindowView.findViewById(R.id.status_bar_container); + } + + /** Returns a fragment host manager for the status bar window view. */ + public FragmentHostManager getFragmentHostManager() { + return FragmentHostManager.get(mStatusBarWindowView); + } + + /** + * Provides an updated animation controller if we're animating a view in the status bar. + * + * This is needed because we have to make sure that the status bar window matches the full + * screen during the animation and that we are expanding the view below the other status bar + * text. + * + * @param rootView the root view of the animation + * @param animationController the default animation controller to use + * @return If the animation is on a view in the status bar, returns an Optional containing an + * updated animation controller that handles status-bar-related animation details. Returns an + * empty optional if the animation is *not* on a view in the status bar. + */ + public Optional<ActivityLaunchAnimator.Controller> wrapAnimationControllerIfInStatusBar( + View rootView, ActivityLaunchAnimator.Controller animationController) { + if (rootView != mStatusBarWindowView) { + return Optional.empty(); + } + + animationController.setLaunchContainer(mLaunchAnimationContainer); + return Optional.of(new DelegateLaunchAnimatorController(animationController) { + @Override + public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { + getDelegate().onLaunchAnimationStart(isExpandingFullyAbove); + setLaunchAnimationRunning(true); + } + + @Override + public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) { + getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove); + setLaunchAnimationRunning(false); + } + }); + } + private WindowManager.LayoutParams getBarLayoutParams(int rotation) { WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation); lp.paramsForRotation = new WindowManager.LayoutParams[4]; @@ -214,21 +272,11 @@ public class StatusBarWindowController { } /** - * Return the container in which we should run launch animations started from the status bar and - * expanding into the opening window. - * - * @see #setLaunchAnimationRunning - */ - public ViewGroup getLaunchAnimationContainer() { - return mLaunchAnimationContainer; - } - - /** * Set whether a launch animation is currently running. If true, this will ensure that the * window matches its parent height so that the animation is not clipped by the normal status * bar height. */ - public void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) { + private void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) { if (isLaunchAnimationRunning == mCurrentState.mIsLaunchAnimationRunning) { return; } @@ -246,7 +294,7 @@ public class StatusBarWindowController { applyForceStatusBarVisibleFlag(state); applyHeight(state); if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { - mWindowManager.updateViewLayout(mStatusBarView, mLp); + mWindowManager.updateViewLayout(mStatusBarWindowView, mLp); } } @@ -266,4 +314,14 @@ public class StatusBarWindowController { mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; } } + + private ViewGroup createWindowView(Context context) { + ViewGroup view = (ViewGroup) LayoutInflater.from(context).inflate( + R.layout.super_status_bar, /* root= */ null); + if (view == null) { + throw new IllegalStateException( + "R.layout.super_status_bar could not be properly inflated"); + } + return view; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 18aa6893e7bc..cf4aaba107cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -22,11 +22,15 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Configuration; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.SystemProperties; import android.os.UserHandle; import android.util.TypedValue; import android.view.ViewGroup; +import android.view.ViewRootImpl; import android.view.Window; import android.view.WindowInsets.Type; import android.view.WindowManager; @@ -35,6 +39,7 @@ import android.view.WindowManager.LayoutParams; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.DialogListener; +import com.android.systemui.animation.DialogListener.DismissReason; import com.android.systemui.animation.ListenableDialog; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -48,7 +53,8 @@ import java.util.Set; * The SystemUIDialog registers a listener for the screen off / close system dialogs broadcast, * and dismisses itself when it receives the broadcast. */ -public class SystemUIDialog extends AlertDialog implements ListenableDialog { +public class SystemUIDialog extends AlertDialog implements ListenableDialog, + ViewRootImpl.ConfigChangedCallback { // TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on. private static final String FLAG_TABLET_DIALOG_WIDTH = "persist.systemui.flag_tablet_dialog_width"; @@ -56,12 +62,22 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog { private final Context mContext; private final DismissReceiver mDismissReceiver; private final Set<DialogListener> mDialogListeners = new LinkedHashSet<>(); + private final Handler mHandler = new Handler(); + + private int mLastWidth = Integer.MIN_VALUE; + private int mLastHeight = Integer.MIN_VALUE; + private int mLastConfigurationWidthDp = -1; + private int mLastConfigurationHeightDp = -1; public SystemUIDialog(Context context) { this(context, R.style.Theme_SystemUI_Dialog); } public SystemUIDialog(Context context, int theme) { + this(context, theme, true /* dismissOnDeviceLock */); + } + + public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) { super(context, theme); mContext = context; @@ -70,18 +86,57 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog { attrs.setTitle(getClass().getSimpleName()); getWindow().setAttributes(attrs); - mDismissReceiver = new DismissReceiver(this); + mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Set the dialog window size. - getWindow().setLayout(getDialogWidth(), ViewGroup.LayoutParams.WRAP_CONTENT); + Configuration config = getContext().getResources().getConfiguration(); + mLastConfigurationWidthDp = config.screenWidthDp; + mLastConfigurationHeightDp = config.screenHeightDp; + updateWindowSize(); + } + + private void updateWindowSize() { + // Only the thread that created this dialog can update its window size. + if (Looper.myLooper() != mHandler.getLooper()) { + mHandler.post(this::updateWindowSize); + return; + } + + int width = getWidth(); + int height = getHeight(); + if (width == mLastWidth && height == mLastHeight) { + return; + } + + mLastWidth = width; + mLastHeight = height; + getWindow().setLayout(width, height); + + for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) { + listener.onSizeChanged(); + } + } + + @Override + public void onConfigurationChanged(Configuration configuration) { + if (mLastConfigurationWidthDp != configuration.screenWidthDp + || mLastConfigurationHeightDp != configuration.screenHeightDp) { + mLastConfigurationWidthDp = configuration.screenWidthDp; + mLastConfigurationHeightDp = configuration.compatScreenWidthDp; + + updateWindowSize(); + } } - private int getDialogWidth() { + /** + * Return this dialog width. This method will be invoked when this dialog is created and when + * the device configuration changes, and the result will be used to resize this dialog window. + */ + protected int getWidth() { boolean isOnTablet = mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600; if (!isOnTablet) { @@ -108,16 +163,36 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog { } } + /** + * Return this dialog height. This method will be invoked when this dialog is created and when + * the device configuration changes, and the result will be used to resize this dialog window. + */ + protected int getHeight() { + return ViewGroup.LayoutParams.WRAP_CONTENT; + } + @Override protected void onStart() { super.onStart(); - mDismissReceiver.register(); + + if (mDismissReceiver != null) { + mDismissReceiver.register(); + } + + // Listen for configuration changes to resize this dialog window. This is mostly necessary + // for foldables that often go from large <=> small screen when folding/unfolding. + ViewRootImpl.addConfigCallback(this); } @Override protected void onStop() { super.onStop(); - mDismissReceiver.unregister(); + + if (mDismissReceiver != null) { + mDismissReceiver.unregister(); + } + + ViewRootImpl.removeConfigCallback(this); } @Override @@ -132,10 +207,14 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog { @Override public void dismiss() { + dismiss(DismissReason.UNKNOWN); + } + + private void dismiss(DismissReason reason) { super.dismiss(); for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) { - listener.onDismiss(); + listener.onDismiss(reason); } } @@ -251,7 +330,11 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog { @Override public void onReceive(Context context, Intent intent) { - mDialog.dismiss(); + if (mDialog instanceof SystemUIDialog) { + ((SystemUIDialog) mDialog).dismiss(DismissReason.DEVICE_LOCKED); + } else { + mDialog.dismiss(); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIHostDialogProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIHostDialogProvider.kt index 6a49a6da0d62..4f18f8c597b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIHostDialogProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIHostDialogProvider.kt @@ -3,6 +3,7 @@ package com.android.systemui.statusbar.phone import android.app.Dialog import android.content.Context import android.os.Bundle +import android.view.ViewGroup import com.android.systemui.animation.HostDialogProvider /** An implementation of [HostDialogProvider] to be used when animating SysUI dialogs. */ @@ -16,12 +17,18 @@ class SystemUIHostDialogProvider : HostDialogProvider { return SystemUIHostDialog(context, theme, onCreateCallback, dismissOverride) } + /** + * This host dialog is a SystemUIDialog so that it's displayed above all SystemUI windows. Note + * that it is not automatically dismissed when the device is locked, but only when the hosted + * (original) dialog is dismissed. That way, the behavior of the dialog (dismissed when locking + * or not) is consistent with when the dialog is shown with or without the dialog animator. + */ private class SystemUIHostDialog( context: Context, theme: Int, private val onCreateCallback: () -> Unit, private val dismissOverride: (() -> Unit) -> Unit - ) : SystemUIDialog(context, theme) { + ) : SystemUIDialog(context, theme, false /* dismissOnDeviceLock */) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) onCreateCallback() @@ -32,5 +39,13 @@ class SystemUIHostDialogProvider : HostDialogProvider { super.dismiss() } } + + override fun getWidth(): Int { + return ViewGroup.LayoutParams.MATCH_PARENT + } + + override fun getHeight(): Int { + return ViewGroup.LayoutParams.MATCH_PARENT + } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index e2332e923aa4..cddde6487ba2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -161,6 +161,8 @@ class UnlockedScreenOffAnimationController @Inject constructor( // Done going to sleep, reset this flag. decidedToAnimateGoingToSleep = null + // We need to unset the listener. These are persistent for future animators + keyguardView.animate().setListener(null) } }) .start() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index c452a486cbe1..4f4342606194 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -29,7 +29,6 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.InitController; import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; @@ -48,7 +47,7 @@ import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.recents.ScreenPinningRequest; -import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -68,6 +67,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; @@ -98,9 +98,9 @@ import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -157,11 +157,13 @@ public interface StatusBarPhoneModule { FalsingManager falsingManager, FalsingCollector falsingCollector, BroadcastDispatcher broadcastDispatcher, + NotifShadeEventSource notifShadeEventSource, NotificationEntryManager notificationEntryManager, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, + PanelExpansionStateManager panelExpansionStateManager, KeyguardViewMediator keyguardViewMediator, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @@ -203,7 +205,6 @@ public interface StatusBarPhoneModule { StatusBarNotificationActivityStarter.Builder statusBarNotificationActivityStarterBuilder, ShadeController shadeController, - StatusBarWindowView statusBarWindowView, StatusBarKeyguardViewManager statusBarKeyguardViewManager, ViewMediatorCallback viewMediatorCallback, InitController initController, @@ -220,7 +221,7 @@ public interface StatusBarPhoneModule { Lazy<NotificationShadeDepthController> notificationShadeDepthController, StatusBarTouchableRegionManager statusBarTouchableRegionManager, NotificationIconAreaController notificationIconAreaController, - BrightnessSlider.Factory brightnessSliderFactory, + BrightnessSliderController.Factory brightnessSliderFactory, UnfoldTransitionConfig unfoldTransitionConfig, Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation, Lazy<NaturalRotationUnfoldProgressProvider> naturalRotationUnfoldProgressProvider, @@ -242,8 +243,7 @@ public interface StatusBarPhoneModule { Optional<StartingSurface> startingSurfaceOptional, TunerService tunerService, DumpManager dumpManager, - ActivityLaunchAnimator activityLaunchAnimator, - DialogLaunchAnimator dialogLaunchAnimator) { + ActivityLaunchAnimator activityLaunchAnimator) { return new StatusBar( context, notificationsController, @@ -260,11 +260,13 @@ public interface StatusBarPhoneModule { falsingManager, falsingCollector, broadcastDispatcher, + notifShadeEventSource, notificationEntryManager, notificationGutsManager, notificationLogger, notificationInterruptStateProvider, notificationViewHierarchyManager, + panelExpansionStateManager, keyguardViewMediator, displayMetrics, metricsLogger, @@ -305,7 +307,6 @@ public interface StatusBarPhoneModule { lightsOutNotifController, statusBarNotificationActivityStarterBuilder, shadeController, - statusBarWindowView, statusBarKeyguardViewManager, viewMediatorCallback, initController, @@ -343,7 +344,7 @@ public interface StatusBarPhoneModule { startingSurfaceOptional, tunerService, dumpManager, - activityLaunchAnimator, - dialogLaunchAnimator); + activityLaunchAnimator + ); } } 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 9de0c46ff078..2765fe37f846 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 @@ -19,11 +19,13 @@ package com.android.systemui.statusbar.phone.dagger; import android.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewStub; import com.android.keyguard.LockIconView; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.biometrics.AuthRippleView; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent; @@ -126,9 +128,16 @@ public abstract class StatusBarViewModule { @Provides @Named(SPLIT_SHADE_HEADER) @StatusBarComponent.StatusBarScope - public static View getSlitShadeStatusBarView( - NotificationShadeWindowView notificationShadeWindowView) { - return notificationShadeWindowView.findViewById(R.id.split_shade_status_bar); + public static View getSplitShadeStatusBarView( + NotificationShadeWindowView notificationShadeWindowView, + FeatureFlags featureFlags) { + ViewStub stub = notificationShadeWindowView.findViewById(R.id.qs_header_stub); + int layoutId = featureFlags.useCombinedQSHeaders() + ? R.layout.combined_qs_header + : R.layout.split_shade_header; + stub.setLayoutResource(layoutId); + View v = stub.inflate(); + return v; } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 3806d9a2925c..31cc823c54ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -86,7 +86,7 @@ class OngoingCallController @Inject constructor( // // TODO(b/183229367): Remove this function override when b/178406514 is fixed. override fun onEntryAdded(entry: NotificationEntry) { - onEntryUpdated(entry) + onEntryUpdated(entry, true) } override fun onEntryUpdated(entry: NotificationEntry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelExpansionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionListener.java index 655a25d22337..b9f806d201b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelExpansionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionListener.java @@ -11,28 +11,21 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.statusbar.phone.panelstate; -/** - * Panel and QS expansion callbacks. - */ +/** A listener interface to be notified of expansion events for the notification panel. */ public interface PanelExpansionListener { /** * Invoked whenever the notification panel expansion changes, at every animation frame. * This is the main expansion that happens when the user is swiping up to dismiss the - * lock screen. + * lock screen and swiping to pull down the notification shade. * - * @param expansion 0 when collapsed, 1 when expanded. + * @param fraction 0 when collapsed, 1 when fully expanded. + * @param expanded true if the panel should be considered expanded. * @param tracking {@code true} when the user is actively dragging the panel. */ - void onPanelExpansionChanged(float expansion, boolean tracking); - - /** - * Invoked whenever the QS expansion changes, at every animation frame. - * @param expansion 0 when collapsed, 1 when expanded. - */ - default void onQsExpansionChanged(float expansion) {}; + void onPanelExpansionChanged(float fraction, boolean expanded, boolean tracking); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt new file mode 100644 index 000000000000..aa748b00d570 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt @@ -0,0 +1,64 @@ +/* + * 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.systemui.statusbar.phone.panelstate + +import androidx.annotation.FloatRange +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * A class responsible for managing the notification panel's current state. + * + * TODO(b/200063118): Move [PanelBar.panelExpansionChanged] logic to this class and make this class + * the one source of truth for the state of panel expansion. + */ +@SysUISingleton +class PanelExpansionStateManager @Inject constructor() { + + private val listeners: MutableList<PanelExpansionListener> = mutableListOf() + + @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f + private var expanded: Boolean = false + private var tracking: Boolean = false + + /** + * Adds a listener that will be notified when the panel expansion has changed. + * + * Listener will also be immediately notified with the current values. + */ + fun addListener(listener: PanelExpansionListener) { + listeners.add(listener) + listener.onPanelExpansionChanged(fraction, expanded, tracking) + } + + /** Removes a listener. */ + fun removeListener(listener: PanelExpansionListener) { + listeners.remove(listener) + } + + /** Called when the panel expansion has changed. Notifies all listeners of change. */ + fun onPanelExpansionChanged( + @FloatRange(from = 0.0, to = 1.0) fraction: Float, + expanded: Boolean, + tracking: Boolean + ) { + this.fraction = fraction + this.expanded = expanded + this.tracking = tracking + listeners.forEach { it.onPanelExpansionChanged(fraction, expanded, tracking) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index 1e5251196379..5bd20ff2d090 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -26,7 +26,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import com.android.systemui.R; -import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.ToggleSlider; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; @@ -46,8 +46,8 @@ public class BrightnessMirrorController private final NotificationPanelViewController mNotificationPanel; private final NotificationShadeDepthController mDepthController; private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>(); - private final BrightnessSlider.Factory mToggleSliderFactory; - private BrightnessSlider mToggleSliderController; + private final BrightnessSliderController.Factory mToggleSliderFactory; + private BrightnessSliderController mToggleSliderController; private final int[] mInt2Cache = new int[2]; private FrameLayout mBrightnessMirror; private int mBrightnessMirrorBackgroundPadding; @@ -56,7 +56,7 @@ public class BrightnessMirrorController public BrightnessMirrorController(NotificationShadeWindowView statusBarWindow, NotificationPanelViewController notificationPanelViewController, NotificationShadeDepthController notificationShadeDepthController, - BrightnessSlider.Factory factory, + BrightnessSliderController.Factory factory, @NonNull Consumer<Boolean> visibilityCallback) { mStatusBarWindow = statusBarWindow; mToggleSliderFactory = factory; @@ -135,9 +135,10 @@ public class BrightnessMirrorController reinflate(); } - private BrightnessSlider setMirrorLayout() { + private BrightnessSliderController setMirrorLayout() { Context context = mBrightnessMirror.getContext(); - BrightnessSlider controller = mToggleSliderFactory.create(context, mBrightnessMirror); + BrightnessSliderController controller = mToggleSliderFactory.create(context, + mBrightnessMirror); controller.init(); mBrightnessMirror.addView(controller.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index a8097c4d74b0..e0b0dd36ccd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -38,11 +38,10 @@ import com.android.systemui.R; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.util.ListenerSet; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashSet; /** * A manager which handles heads up notifications which is a special mode where @@ -52,7 +51,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { private static final String TAG = "HeadsUpManager"; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; - protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); + protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>(); protected final Context mContext; @@ -118,7 +117,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { * Adds an OnHeadUpChangedListener to observe events. */ public void addListener(@NonNull OnHeadsUpChangedListener listener) { - mListeners.add(listener); + mListeners.addIfAbsent(listener); } /** @@ -158,7 +157,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(), entry.getSbn().getPackageName(), entry.getSbn().getInstanceId()); } - for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { + for (OnHeadsUpChangedListener listener : mListeners) { if (isPinned) { listener.onHeadsUpPinned(entry); } else { @@ -178,7 +177,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(true); setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry)); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */); - for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { + for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, true); } } @@ -189,7 +188,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(false); setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */); - for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { + for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } } @@ -207,7 +206,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } - for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { + for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 5d7d4809dd57..aa8d95fdb625 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -108,36 +108,36 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private final SendButtonTextWatcher mTextWatcher; private final TextView.OnEditorActionListener mEditorActionHandler; - private final UiEventLogger mUiEventLogger; - private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; - private final List<OnFocusChangeListener> mEditTextFocusChangeListeners = new ArrayList<>(); - private final List<OnSendRemoteInputListener> mOnSendListeners = new ArrayList<>(); + private final ArrayList<OnSendRemoteInputListener> mOnSendListeners = new ArrayList<>(); + private final ArrayList<Consumer<Boolean>> mOnVisibilityChangedListeners = new ArrayList<>(); + private final ArrayList<OnFocusChangeListener> mEditTextFocusChangeListeners = + new ArrayList<>(); + private RemoteEditText mEditText; private ImageButton mSendButton; private GradientDrawable mContentBackground; private ProgressBar mProgressBar; - private PendingIntent mPendingIntent; - private RemoteInput[] mRemoteInputs; - private RemoteInput mRemoteInput; - private RemoteInputController mController; - - private NotificationEntry mEntry; - - private boolean mRemoved; - + private ImageView mDelete; + private ImageView mDeleteBg; + // TODO(b/193539698): remove reveal param fields, turn them into parameters where needed private int mRevealCx; private int mRevealCy; private int mRevealR; - private boolean mColorized; private int mTint; - private boolean mResetting; - private NotificationViewWrapper mWrapper; - private Consumer<Boolean> mOnVisibilityChangedListener; + + // TODO(b/193539698): move these to a Controller + private RemoteInputController mController; + private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; + private final UiEventLogger mUiEventLogger; + private NotificationEntry mEntry; + private PendingIntent mPendingIntent; + private RemoteInput mRemoteInput; + private RemoteInput[] mRemoteInputs; private NotificationRemoteInputManager.BouncerChecker mBouncerChecker; - private ImageView mDelete; - private ImageView mDeleteBg; + private boolean mRemoved; + private NotificationViewWrapper mWrapper; /** * Enum for logged notification remote input UiEvents. @@ -382,7 +382,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private void sendRemoteInput(Intent intent) { if (mBouncerChecker != null && mBouncerChecker.showBouncerIfNecessary()) { mEditText.hideIme(); - for (OnSendRemoteInputListener listener : mOnSendListeners) { + for (OnSendRemoteInputListener listener : new ArrayList<>(mOnSendListeners)) { listener.onSendRequestBounced(); } return; @@ -399,7 +399,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mController.remoteInputSent(mEntry); mEntry.setHasSentReply(); - for (OnSendRemoteInputListener listener : mOnSendListeners) { + for (OnSendRemoteInputListener listener : new ArrayList<>(mOnSendListeners)) { listener.onSendRemoteInput(); } @@ -760,15 +760,32 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mWrapper = wrapper; } - public void setOnVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) { - mOnVisibilityChangedListener = visibilityChangedListener; + /** + * Register a listener to be notified when this view's visibility changes. + * + * Specifically, the passed {@link Consumer} will receive {@code true} when + * {@link #getVisibility()} would return {@link View#VISIBLE}, and {@code false} it would return + * any other value. + */ + public void addOnVisibilityChangedListener(Consumer<Boolean> listener) { + mOnVisibilityChangedListeners.add(listener); + } + + /** + * Unregister a listener previously registered via + * {@link #addOnVisibilityChangedListener(Consumer)}. + */ + public void removeOnVisibilityChangedListener(Consumer<Boolean> listener) { + mOnVisibilityChangedListeners.remove(listener); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); - if (changedView == this && mOnVisibilityChangedListener != null) { - mOnVisibilityChangedListener.accept(visibility == VISIBLE); + if (changedView == this) { + for (Consumer<Boolean> listener : new ArrayList<>(mOnVisibilityChangedListeners)) { + listener.accept(visibility == VISIBLE); + } // Hide soft-keyboard when the input view became invisible // (i.e. The notification shade collapsed by pressing the home key) if (visibility != VISIBLE && !mEditText.isVisibleToUser() diff --git a/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt new file mode 100644 index 000000000000..9f33c271881d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt @@ -0,0 +1,75 @@ +/* + * 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.systemui.util + +import android.util.IndentingPrintWriter +import android.view.View +import java.io.PrintWriter +import java.util.function.Consumer + +/** + * Run some code that will print to an [IndentingPrintWriter] that wraps the given [PrintWriter]. + * + * If the given [PrintWriter] is an [IndentingPrintWriter], the block will be passed that same + * instance with [IndentingPrintWriter.increaseIndent] having been called, and calling + * [IndentingPrintWriter.decreaseIndent] after completion of the block, so the passed [PrintWriter] + * should not be used before the block completes. + */ +inline fun PrintWriter.withIndenting(block: (IndentingPrintWriter) -> Unit) { + if (this is IndentingPrintWriter) { + this.withIncreasedIndent { block(this) } + } else { + block(IndentingPrintWriter(this)) + } +} + +/** + * Run some code that will print to an [IndentingPrintWriter] that wraps the given [PrintWriter]. + * + * If the given [PrintWriter] is an [IndentingPrintWriter], the block will be passed that same + * instance with [IndentingPrintWriter.increaseIndent] having been called, and calling + * [IndentingPrintWriter.decreaseIndent] after completion of the block, so the passed [PrintWriter] + * should not be used before the block completes. + */ +fun PrintWriter.withIndenting(consumer: Consumer<IndentingPrintWriter>) { + if (this is IndentingPrintWriter) { + this.withIncreasedIndent { consumer.accept(this) } + } else { + consumer.accept(IndentingPrintWriter(this)) + } +} + +/** + * Run some code inside a block, with [IndentingPrintWriter.increaseIndent] having been called on + * the given argument, and calling [IndentingPrintWriter.decreaseIndent] after completion. + */ +inline fun IndentingPrintWriter.withIncreasedIndent(block: () -> Unit) { + this.increaseIndent() + try { + block() + } finally { + this.decreaseIndent() + } +} + +/** Return a readable string for the visibility */ +fun visibilityString(@View.Visibility visibility: Int): String = when (visibility) { + View.GONE -> "gone" + View.VISIBLE -> "visible" + View.INVISIBLE -> "invisible" + else -> "unknown:$visibility" +} diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt new file mode 100644 index 000000000000..0f4193e94196 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt @@ -0,0 +1,47 @@ +/* + * 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.systemui.util + +import java.util.concurrent.CopyOnWriteArrayList + +/** + * A collection of listeners, observers, callbacks, etc. + * + * This container is optimized for infrequent mutation and frequent iteration, with thread safety + * and reentrant-safety guarantees as well. + */ +class ListenerSet<E> : Iterable<E> { + private val listeners: CopyOnWriteArrayList<E> = CopyOnWriteArrayList() + + /** + * A thread-safe, reentrant-safe method to add a listener. + * Does nothing if the listener is already in the set. + */ + fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element) + + /** + * A thread-safe, reentrant-safe method to remove a listener. + */ + fun remove(element: E): Boolean = listeners.remove(element) + + /** + * Returns an iterator over the listeners currently in the set. Note that to ensure + * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes + * made to the set after the iterator is constructed. + */ + override fun iterator(): Iterator<E> = listeners.iterator() +} diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt new file mode 100644 index 000000000000..5b16ae999aa3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt @@ -0,0 +1,32 @@ +/* + * 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.systemui.util + +import android.os.Trace + +/** + * Run a block within a [Trace] section. + * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block. + */ +inline fun <T> traceSection(tag: String, block: () -> T): T { + Trace.beginSection(tag) + try { + return block() + } finally { + Trace.endSection() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java index e639313ac649..5568f64f5084 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java @@ -17,6 +17,7 @@ package com.android.systemui.util.sensors; import android.hardware.SensorManager; +import android.os.Build; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -56,7 +57,7 @@ import javax.inject.Inject; */ class ProximitySensorImpl implements ProximitySensor { private static final String TAG = "ProxSensor"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE; private static final long SECONDARY_PING_INTERVAL_MS = 5000; ThresholdSensor mPrimaryThresholdSensor; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 0a33930d404c..bdc62c974f5a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -74,6 +74,8 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.recents.RecentTasks; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -144,8 +146,11 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, - Context context, SizeCompatUIController sizeCompatUI) { - return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI); + Context context, + SizeCompatUIController sizeCompatUI, + Optional<RecentTasksController> recentTasksOptional + ) { + return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI, recentTasksOptional); } @WMSingleton @@ -410,6 +415,28 @@ public abstract class WMShellBaseModule { abstract PipTouchHandler optionalPipTouchHandler(); // + // Recent tasks + // + + @WMSingleton + @Provides + static Optional<RecentTasks> provideRecentTasks( + Optional<RecentTasksController> recentTasksController) { + return recentTasksController.map((controller) -> controller.asRecentTasks()); + } + + @WMSingleton + @Provides + static Optional<RecentTasksController> provideRecentTasksController( + Context context, + TaskStackListenerImpl taskStackListener, + @ShellMainThread ShellExecutor mainExecutor + ) { + return Optional.ofNullable( + RecentTasksController.create(context, taskStackListener, mainExecutor)); + } + + // // Shell transitions // @@ -555,13 +582,13 @@ public abstract class WMShellBaseModule { DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional<BubbleController> bubblesOptional, - Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional<FullscreenUnfoldController> appUnfoldTransitionController, Optional<Optional<FreeformTaskListener>> freeformTaskListener, + Optional<RecentTasksController> recentTasksOptional, Transitions transitions, StartingWindowController startingWindow, @ShellMainThread ShellExecutor mainExecutor) { @@ -571,13 +598,13 @@ public abstract class WMShellBaseModule { dragAndDropController, shellTaskOrganizer, bubblesOptional, - legacySplitScreenOptional, splitScreenOptional, appPairsOptional, pipTouchHandlerOptional, fullscreenTaskListener, appUnfoldTransitionController, freeformTaskListener, + recentTasksOptional, transitions, startingWindow, mainExecutor); @@ -603,9 +630,10 @@ public abstract class WMShellBaseModule { Optional<OneHandedController> oneHandedOptional, Optional<HideDisplayCutoutController> hideDisplayCutout, Optional<AppPairsController> appPairsOptional, + Optional<RecentTasksController> recentTasksOptional, @ShellMainThread ShellExecutor mainExecutor) { return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, - hideDisplayCutout, appPairsOptional, mainExecutor); + hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index 5bcf828afe9f..54278066b5d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -12,6 +12,7 @@ import android.view.WindowManager import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogListener.DismissReason import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue @@ -63,10 +64,6 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertEquals(1, hostDialogRoot.childCount) assertEquals(dialog.contentView, hostDialogRoot.getChildAt(0)) - // If we are dozing, the host dialog window also fades out. - runOnMainThreadAndWaitForIdleSync { dialogLaunchAnimator.onDozeAmountChanged(0.5f) } - assertTrue(hostDialog.window!!.decorView.alpha < 1f) - // Hiding/showing/dismissing the dialog should hide/show/dismiss the host dialog given that // it's a ListenableDialog. runOnMainThreadAndWaitForIdleSync { dialog.hide() } @@ -164,7 +161,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { override fun dismiss() { super.dismiss() - notifyListeners { onDismiss() } + notifyListeners { onDismiss(DismissReason.UNKNOWN) } } override fun hide() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt index ca7d506dcc78..5fee7fbf8705 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.biometrics import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN import android.hardware.biometrics.SensorProperties import android.hardware.display.DisplayManager @@ -183,16 +182,7 @@ class SidefpsControllerTest : SysuiTestCase() { @Test fun testIgnoredForKeyguard() { - testIgnoredFor(REASON_AUTH_KEYGUARD) - } - - @Test - fun testIgnoredForSettings() { - testIgnoredFor(REASON_AUTH_SETTINGS) - } - - private fun testIgnoredFor(reason: Int) { - overlayController.show(SENSOR_ID, reason) + overlayController.show(SENSOR_ID, REASON_AUTH_KEYGUARD) executor.runAllReady() verify(windowManager, never()).addView(any(), any()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index deabda35aeae..cfac9cb592a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -66,6 +66,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.Execution; @@ -219,7 +220,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mWindowManager, mStatusBarStateController, mFgExecutor, - Optional.of(mStatusBar), + new PanelExpansionStateManager(), mStatusBarKeyguardViewManager, mDumpManager, mKeyguardUpdateMonitor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index 2821f3d21606..6f0456ef8f5b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -41,9 +41,10 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -57,7 +58,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Optional; import java.util.List; @SmallTest @@ -72,7 +72,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock - private StatusBar mStatusBar; + private PanelExpansionStateManager mPanelExpansionStateManager; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock @@ -101,8 +101,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor; private StatusBarStateController.StateListener mStatusBarStateListener; - @Captor private ArgumentCaptor<StatusBar.ExpansionChangedListener> mExpansionListenerCaptor; - private List<StatusBar.ExpansionChangedListener> mExpansionListeners; + @Captor private ArgumentCaptor<PanelExpansionListener> mExpansionListenerCaptor; + private List<PanelExpansionListener> mExpansionListeners; @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor> mAltAuthInterceptorCaptor; @@ -121,7 +121,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mController = new UdfpsKeyguardViewController( mView, mStatusBarStateController, - Optional.of(mStatusBar), + mPanelExpansionStateManager, mStatusBarKeyguardViewManager, mKeyguardUpdateMonitor, mDumpManager, @@ -170,8 +170,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mController.onViewDetached(); verify(mStatusBarStateController).removeCallback(mStatusBarStateListener); - for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) { - verify(mStatusBar).removeExpansionChangedListener(listener); + for (PanelExpansionListener listener : mExpansionListeners) { + verify(mPanelExpansionStateManager).removeListener(listener); } verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback); } @@ -434,16 +434,16 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { } private void captureExpansionListeners() { - verify(mStatusBar, times(2)) - .addExpansionChangedListener(mExpansionListenerCaptor.capture()); + verify(mPanelExpansionStateManager, times(2)) + .addListener(mExpansionListenerCaptor.capture()); // first (index=0) is from super class, UdfpsAnimationViewController. // second (index=1) is from UdfpsKeyguardViewController mExpansionListeners = mExpansionListenerCaptor.getAllValues(); } - private void updateStatusBarExpansion(float expansion, boolean expanded) { - for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) { - listener.onExpansionChanged(expansion, expanded); + private void updateStatusBarExpansion(float fraction, boolean expanded) { + for (PanelExpansionListener listener : mExpansionListeners) { + listener.onPanelExpansionChanged(fraction, expanded, /* tracking= */ false); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java index 172dcda5321f..2fa32ba1fe75 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java @@ -18,23 +18,61 @@ package com.android.systemui.flags; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.content.Context; + import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.settings.SecureSettings; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow + * overriding, and should never return any value other than the one provided as the default. + */ @SmallTest public class FeatureFlagManagerTest extends SysuiTestCase { FeatureFlagManager mFeatureFlagManager; + @Mock private FlagManager mFlagManager; + @Mock private SecureSettings mSecureSettings; + @Mock private Context mContext; + @Mock private DumpManager mDumpManager; + @Before public void setup() { MockitoAnnotations.initMocks(this); - mFeatureFlagManager = new FeatureFlagManager(); + mFeatureFlagManager = new FeatureFlagManager(mSecureSettings, mContext, mDumpManager); + } + + @After + public void onFinished() { + // SecureSettings and Context are provided for constructor consistency with the + // debug version of the FeatureFlagManager, but should never be used. + verifyZeroInteractions(mSecureSettings, mContext); + // The dump manager should be registered with even for the release version, but that's it. + verify(mDumpManager).registerDumpable(anyString(), any()); + verifyNoMoreInteractions(mDumpManager); } @Test @@ -43,4 +81,31 @@ public class FeatureFlagManagerTest extends SysuiTestCase { // Again, nothing changes. assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse(); } + + @Test + public void testDump() { + // Even if a flag is set before + mFeatureFlagManager.setEnabled(1, true); + + // WHEN the flags have been accessed + assertFalse(mFeatureFlagManager.isEnabled(1, false)); + assertTrue(mFeatureFlagManager.isEnabled(2, true)); + + // Even if a flag is set after + mFeatureFlagManager.setEnabled(2, false); + + // THEN the dump contains the flags and the default values + String dump = dumpToString(); + assertThat(dump).contains(" sysui_flag_1: false\n"); + assertThat(dump).contains(" sysui_flag_2: true\n"); + } + + private String dumpToString() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + mFeatureFlagManager.dump(mock(FileDescriptor.class), pw, new String[0]); + pw.flush(); + String dump = sw.toString(); + return dump; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java index a6ff2e8d2e15..85bc634c28b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.view.Display; import android.view.View; import android.view.WindowInsetsController; @@ -31,6 +32,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; +import com.android.systemui.shared.rotation.RotationButton; +import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.statusbar.policy.RotationLockController; import org.junit.Before; @@ -39,6 +42,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; +import java.util.function.Supplier; + /** atest NavigationBarRotationContextTest */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -50,6 +55,8 @@ public class NavigationBarRotationContextTest extends SysuiTestCase { InstrumentationRegistry.getContext(), getLeakCheck()); private RotationButtonController mRotationButtonController; private RotationButton mRotationButton; + private int mWindowRotation = DEFAULT_ROTATE; + private Supplier<Integer> mWindowRotationSupplier = () -> mWindowRotation; @Before public void setup() { @@ -58,7 +65,15 @@ public class NavigationBarRotationContextTest extends SysuiTestCase { final View view = new View(mContext); mRotationButton = mock(RotationButton.class); - mRotationButtonController = new RotationButtonController(mContext, 0, 0); + mRotationButtonController = new RotationButtonController(mContext, + /* lightIconColor */ 0, + /* darkIconColor */ 0, + /* iconCcwStart0 */ 0, + /* iconCcwStart90 */ 0, + /* iconCwStart0 */ 0, + /* iconCwStart90 */ 0, + mWindowRotationSupplier + ); mRotationButtonController.setRotationButton(mRotationButton, new RotationButton.RotationButtonUpdatesCallback() { @Override @@ -77,16 +92,16 @@ public class NavigationBarRotationContextTest extends SysuiTestCase { @Test public void testOnInvalidRotationProposal() { - mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, - false /* isValid */); + mWindowRotation = DEFAULT_ROTATE + 1; + mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, false /* isValid */); verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState( false /* visible */); } @Test public void testOnSameRotationProposal() { - mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE, - true /* isValid */); + mWindowRotation = DEFAULT_ROTATE; + mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */); verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState( false /* visible */); } @@ -94,17 +109,17 @@ public class NavigationBarRotationContextTest extends SysuiTestCase { @Test public void testOnRotationProposalShowButtonShowNav() { // No navigation bar should not call to set visibility state - mRotationButtonController.onBehaviorChanged( + mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY, WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */); verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( false /* visible */); verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( true /* visible */); + mWindowRotation = DEFAULT_ROTATE + 1; // No navigation bar with rotation change should not call to set visibility state - mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, - true /* isValid */); + mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */); verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( false /* visible */); verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( @@ -124,10 +139,10 @@ public class NavigationBarRotationContextTest extends SysuiTestCase { false /* visible */); verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState( true /* visible */); + mWindowRotation = DEFAULT_ROTATE + 1; // Navigation bar is visible and rotation requested - mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, - true /* isValid */); + mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */); verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState( true /* visible */); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt index 0a2000107053..36e02cb1df06 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt @@ -4,7 +4,8 @@ import android.view.Gravity import android.view.Surface import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculator.Position +import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator +import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index e54a6ec46b9c..d2bba361b1f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -1,7 +1,9 @@ package com.android.systemui.qs -import com.android.systemui.R import android.os.UserManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.ViewUtils import android.view.LayoutInflater import android.view.View import androidx.test.filters.SmallTest @@ -9,6 +11,7 @@ import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.internal.logging.testing.FakeMetricsLogger import com.android.systemui.Dependency +import com.android.systemui.R import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter @@ -19,8 +22,11 @@ import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.tuner.TunerService import com.android.systemui.utils.leaks.FakeTunerService import com.android.systemui.utils.leaks.LeakCheckedTest +import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mock @@ -30,6 +36,8 @@ import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) class FooterActionsControllerTest : LeakCheckedTest() { @Mock private lateinit var userManager: UserManager @@ -53,10 +61,12 @@ class FooterActionsControllerTest : LeakCheckedTest() { private val metricsLogger: MetricsLogger = FakeMetricsLogger() private lateinit var view: FooterActionsView private val falsingManager: FalsingManagerFake = FalsingManagerFake() + private lateinit var testableLooper: TestableLooper @Before fun setUp() { MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES) val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService @@ -69,7 +79,14 @@ class FooterActionsControllerTest : LeakCheckedTest() { globalActionsDialog, uiEventLogger, showPMLiteButton = true, buttonsVisibleState = ExpansionState.EXPANDED) controller.init() - controller.onViewAttached() + ViewUtils.attachView(view) + // View looper is the testable looper associated with the test + testableLooper.processAllMessages() + } + + @After + fun tearDown() { + ViewUtils.detachView(view) } @Test @@ -90,4 +107,19 @@ class FooterActionsControllerTest : LeakCheckedTest() { // Verify Settings wasn't launched. verify<ActivityStarter>(activityStarter, Mockito.never()).startActivity(any(), anyBoolean()) } + + @Test + fun testMultiUserSwitchUpdatedWhenExpansionStarts() { + // When expansion starts, listening is set to true + val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch) + + assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE) + + whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true) + + controller.setListening(true) + testableLooper.processAllMessages() + + assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt new file mode 100644 index 000000000000..e2c6ff996199 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt @@ -0,0 +1,57 @@ +/* + * 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.systemui.qs + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.statusbar.DisableFlagsLogger +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.Mockito.mock +import java.io.PrintWriter +import java.io.StringWriter + +@SmallTest +class QSFragmentDisableFlagsLoggerTest : SysuiTestCase() { + + private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)) + .create("buffer", 10) + private val disableFlagsLogger = DisableFlagsLogger( + listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')), + listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b')) + ) + private val logger = QSFragmentDisableFlagsLogger(buffer, disableFlagsLogger) + + @Test + fun logDisableFlagChange_bufferHasStates() { + val state = DisableFlagsLogger.DisableState(0, 1) + + logger.logDisableFlagChange(state, state) + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + val expectedLogString = disableFlagsLogger.getDisableFlagsString( + old = null, new = state, newAfterLocalModification = state + ) + + assertThat(actualString).contains(expectedLogString) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index c4bab738cc03..30664ba3c5c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -183,6 +183,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mQQSMediaHost, mBypassController, mQsComponentFactory, + mock(QSFragmentDisableFlagsLogger.class), mFalsingManager, mock(DumpManager.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java index 06a4ae096d44..3242adbcfad8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java @@ -42,7 +42,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.brightness.BrightnessController; -import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.ToggleSlider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.tuner.TunerService; @@ -88,9 +88,9 @@ public class QSPanelControllerTest extends SysuiTestCase { @Mock private BrightnessController mBrightnessController; @Mock - private BrightnessSlider.Factory mToggleSliderViewControllerFactory; + private BrightnessSliderController.Factory mToggleSliderViewControllerFactory; @Mock - private BrightnessSlider mBrightnessSlider; + private BrightnessSliderController mBrightnessSliderController; @Mock QSTileImpl mQSTile; @Mock @@ -120,7 +120,7 @@ public class QSPanelControllerTest extends SysuiTestCase { when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile)); when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView); when(mToggleSliderViewControllerFactory.create(any(), any())) - .thenReturn(mBrightnessSlider); + .thenReturn(mBrightnessSliderController); when(mBrightnessControllerFactory.create(any(ToggleSlider.class))) .thenReturn(mBrightnessController); when(mQSTileRevealControllerFactory.create(any(), any())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 92b9f75936b9..f32ac849b000 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -50,6 +50,7 @@ import org.junit.runner.RunWith import org.mockito.Answers import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -247,7 +248,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { controller.init() val captor = argumentCaptor<List<String>>() - verify(view).onAttach(any(), any(), capture(captor)) + verify(view).onAttach(any(), any(), capture(captor), anyBoolean()) assertThat(captor.value).containsExactly( mContext.getString(com.android.internal.R.string.status_bar_mobile) @@ -260,7 +261,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { controller.init() val captor = argumentCaptor<List<String>>() - verify(view).onAttach(any(), any(), capture(captor)) + verify(view).onAttach(any(), any(), capture(captor), anyBoolean()) assertThat(captor.value).containsExactly( mContext.getString(com.android.internal.R.string.status_bar_no_calling), diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt index bceb92894609..2b39354d99e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt @@ -45,7 +45,7 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) -class BrightnessSliderTest : SysuiTestCase() { +class BrightnessSliderControllerTest : SysuiTestCase() { @Mock private lateinit var brightnessSliderView: BrightnessSliderView @@ -66,7 +66,7 @@ class BrightnessSliderTest : SysuiTestCase() { private lateinit var seekBar: SeekBar private var mFalsingManager: FalsingManagerFake = FalsingManagerFake() - private lateinit var mController: BrightnessSlider + private lateinit var mController: BrightnessSliderController @Before fun setUp() { @@ -75,7 +75,7 @@ class BrightnessSliderTest : SysuiTestCase() { whenever(mirrorController.toggleSlider).thenReturn(mirror) whenever(motionEvent.copy()).thenReturn(motionEvent) - mController = BrightnessSlider(brightnessSliderView, mFalsingManager) + mController = BrightnessSliderController(brightnessSliderView, mFalsingManager) mController.init() mController.setOnChangedListener(listener) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt index 096efad50615..38ad6b85f8aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt @@ -86,6 +86,23 @@ class DisableFlagsLoggerTest : SysuiTestCase() { } @Test + fun getDisableFlagsString_nullOld_onlyNewStateLogged() { + val result = disableFlagsLogger.getDisableFlagsString( + old = null, + new = DisableFlagsLogger.DisableState( + 0b001, // abC + 0b01, // mN + ), + ) + + assertThat(result).doesNotContain("Old") + assertThat(result).contains("New: abC.mN") + // We have no state to diff on, so we shouldn't see any diff in parentheses + assertThat(result).doesNotContain("(") + assertThat(result).doesNotContain(")") + } + + @Test fun getDisableFlagsString_nullLocalModification_localModNotLogged() { val result = disableFlagsLogger.getDisableFlagsString( DisableFlagsLogger.DisableState(0, 0), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index ea21aa906ab1..23cca727335e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -20,10 +20,10 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.content.Intent.ACTION_USER_SWITCHED; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index 5944e9c1f391..4ed722470334 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -1,3 +1,18 @@ +/* + * 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.systemui.statusbar; @@ -10,26 +25,25 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; -import android.app.RemoteInputHistoryItem; import android.content.Context; -import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputActiveExtender; -import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputHistoryExtender; -import com.android.systemui.statusbar.NotificationRemoteInputManager.SmartReplyHistoryExtender; +import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputActiveExtender; +import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputHistoryExtender; +import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.SmartReplyHistoryExtender; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -76,13 +90,19 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { private RemoteInputHistoryExtender mRemoteInputHistoryExtender; private SmartReplyHistoryExtender mSmartReplyHistoryExtender; private RemoteInputActiveExtender mRemoteInputActiveExtender; + private TestableNotificationRemoteInputManager.FakeLegacyRemoteInputLifetimeExtender + mLegacyRemoteInputLifetimeExtender; @Before public void setUp() { MockitoAnnotations.initMocks(this); mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext, - mLockscreenUserManager, mSmartReplyController, mEntryManager, + mock(FeatureFlags.class), + mLockscreenUserManager, + mSmartReplyController, + mEntryManager, + mock(RemoteInputNotificationRebuilder.class), () -> Optional.of(mock(StatusBar.class)), mStateController, Handler.createAsync(Looper.myLooper()), @@ -120,6 +140,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { public void testShouldExtendLifetime_remoteInputActive() { when(mController.isRemoteInputActive(mEntry)).thenReturn(true); + assertTrue(mRemoteInputManager.isRemoteInputActive(mEntry)); assertTrue(mRemoteInputActiveExtender.shouldExtendLifetime(mEntry)); } @@ -128,6 +149,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true; when(mController.isSpinning(mEntry.getKey())).thenReturn(true); + assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry)); assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry)); } @@ -136,6 +158,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true; mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime(); + assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry)); assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry)); } @@ -144,6 +167,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true; when(mSmartReplyController.isSendingSmartReply(mEntry.getKey())).thenReturn(true); + assertTrue(mRemoteInputManager.shouldKeepForSmartReplyHistory(mEntry)); assertTrue(mSmartReplyHistoryExtender.shouldExtendLifetime(mEntry)); } @@ -151,124 +175,24 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { public void testNotificationWithRemoteInputActiveIsRemovedOnCollapse() { mRemoteInputActiveExtender.setShouldManageLifetime(mEntry, true /* shouldManage */); - assertEquals(mRemoteInputManager.getEntriesKeptForRemoteInputActive(), + assertEquals(mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive(), Sets.newArraySet(mEntry)); mRemoteInputManager.onPanelCollapsed(); - assertTrue(mRemoteInputManager.getEntriesKeptForRemoteInputActive().isEmpty()); + assertTrue( + mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive().isEmpty()); } - @Test - public void testRebuildWithRemoteInput_noExistingInput_image() { - Uri uri = mock(Uri.class); - String mimeType = "image/jpeg"; - String text = "image inserted"; - StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInputInserted( - mEntry, text, false, mimeType, uri); - RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() - .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); - assertEquals(1, messages.length); - assertEquals(text, messages[0].getText()); - assertEquals(mimeType, messages[0].getMimeType()); - assertEquals(uri, messages[0].getUri()); - } - - @Test - public void testRebuildWithRemoteInput_noExistingInputNoSpinner() { - StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInputInserted( - mEntry, "A Reply", false, null, null); - RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() - .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); - assertEquals(1, messages.length); - assertEquals("A Reply", messages[0].getText()); - assertFalse(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); - assertTrue(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); - } - - @Test - public void testRebuildWithRemoteInput_noExistingInputWithSpinner() { - StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInputInserted( - mEntry, "A Reply", true, null, null); - RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() - .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); - assertEquals(1, messages.length); - assertEquals("A Reply", messages[0].getText()); - assertTrue(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); - assertTrue(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); - } - - @Test - public void testRebuildWithRemoteInput_withExistingInput() { - // Setup a notification entry with 1 remote input. - StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInputInserted( - mEntry, "A Reply", false, null, null); - NotificationEntry entry = new NotificationEntryBuilder() - .setSbn(newSbn) - .build(); - - // Try rebuilding to add another reply. - newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInputInserted( - entry, "Reply 2", true, null, null); - RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() - .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); - assertEquals(2, messages.length); - assertEquals("Reply 2", messages[0].getText()); - assertEquals("A Reply", messages[1].getText()); - } - - @Test - public void testRebuildWithRemoteInput_withExistingInput_image() { - // Setup a notification entry with 1 remote input. - Uri uri = mock(Uri.class); - String mimeType = "image/jpeg"; - String text = "image inserted"; - StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInputInserted( - mEntry, text, false, mimeType, uri); - NotificationEntry entry = new NotificationEntryBuilder() - .setSbn(newSbn) - .build(); - - // Try rebuilding to add another reply. - newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInputInserted( - entry, "Reply 2", true, null, null); - RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() - .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); - assertEquals(2, messages.length); - assertEquals("Reply 2", messages[0].getText()); - assertEquals(text, messages[1].getText()); - assertEquals(mimeType, messages[1].getMimeType()); - assertEquals(uri, messages[1].getUri()); - } - - @Test - public void testRebuildNotificationForCanceledSmartReplies() { - // Try rebuilding to remove spinner and hide buttons. - StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationForCanceledSmartReplies(mEntry); - assertFalse(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); - assertTrue(newSbn.getNotification().extras - .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); - } - - private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager { TestableNotificationRemoteInputManager( Context context, + FeatureFlags featureFlags, NotificationLockscreenUserManager lockscreenUserManager, SmartReplyController smartReplyController, NotificationEntryManager notificationEntryManager, + RemoteInputNotificationRebuilder rebuilder, Lazy<Optional<StatusBar>> statusBarOptionalLazy, StatusBarStateController statusBarStateController, Handler mainHandler, @@ -278,9 +202,11 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { DumpManager dumpManager) { super( context, + featureFlags, lockscreenUserManager, smartReplyController, notificationEntryManager, + rebuilder, statusBarOptionalLazy, statusBarStateController, mainHandler, @@ -297,14 +223,28 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { mRemoteInputController = controller; } + @NonNull @Override - protected void addLifetimeExtenders() { - mRemoteInputActiveExtender = new RemoteInputActiveExtender(); - mRemoteInputHistoryExtender = new RemoteInputHistoryExtender(); - mSmartReplyHistoryExtender = new SmartReplyHistoryExtender(); - mLifetimeExtenders.add(mRemoteInputHistoryExtender); - mLifetimeExtenders.add(mSmartReplyHistoryExtender); - mLifetimeExtenders.add(mRemoteInputActiveExtender); + protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender( + Handler mainHandler, + NotificationEntryManager notificationEntryManager, + SmartReplyController smartReplyController) { + mLegacyRemoteInputLifetimeExtender = new FakeLegacyRemoteInputLifetimeExtender(); + return mLegacyRemoteInputLifetimeExtender; } + + class FakeLegacyRemoteInputLifetimeExtender extends LegacyRemoteInputLifetimeExtender { + + @Override + protected void addLifetimeExtenders() { + mRemoteInputActiveExtender = new RemoteInputActiveExtender(); + mRemoteInputHistoryExtender = new RemoteInputHistoryExtender(); + mSmartReplyHistoryExtender = new SmartReplyHistoryExtender(); + mLifetimeExtenders.add(mRemoteInputHistoryExtender); + mLifetimeExtenders.add(mSmartReplyHistoryExtender); + mLifetimeExtenders.add(mRemoteInputActiveExtender); + } + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index dbd5168386de..0bce621c3b02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -119,15 +119,17 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Test fun onPanelExpansionChanged_apliesBlur_ifShade() { - notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */, - false /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = false + ) verify(shadeAnimation).animateTo(eq(maxBlur), any()) } @Test fun onPanelExpansionChanged_animatesBlurIn_ifShade() { - notificationShadeDepthController.onPanelExpansionChanged(0.01f /* expansion */, - false /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 0.01f, expanded = false, tracking = false + ) verify(shadeAnimation).animateTo(eq(maxBlur), any()) } @@ -135,8 +137,9 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { fun onPanelExpansionChanged_animatesBlurOut_ifShade() { onPanelExpansionChanged_animatesBlurIn_ifShade() clearInvocations(shadeAnimation) - notificationShadeDepthController.onPanelExpansionChanged(0f /* expansion */, - false /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 0f, expanded = false, tracking = false + ) verify(shadeAnimation).animateTo(eq(0), any()) } @@ -144,16 +147,19 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { fun onPanelExpansionChanged_animatesBlurOut_ifFlick() { onPanelExpansionChanged_apliesBlur_ifShade() clearInvocations(shadeAnimation) - notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */, - true /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = true + ) verify(shadeAnimation, never()).animateTo(anyInt(), any()) - notificationShadeDepthController.onPanelExpansionChanged(0.9f /* expansion */, - true /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 0.9f, expanded = true, tracking = true + ) verify(shadeAnimation, never()).animateTo(anyInt(), any()) - notificationShadeDepthController.onPanelExpansionChanged(0.8f /* expansion */, - false /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 0.8f, expanded = true, tracking = false + ) verify(shadeAnimation).animateTo(eq(0), any()) } @@ -161,24 +167,28 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { fun onPanelExpansionChanged_animatesBlurIn_ifFlickCancelled() { onPanelExpansionChanged_animatesBlurOut_ifFlick() clearInvocations(shadeAnimation) - notificationShadeDepthController.onPanelExpansionChanged(0.6f /* expansion */, - true /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 0.6f, expanded = true, tracking = true + ) verify(shadeAnimation).animateTo(eq(maxBlur), any()) } @Test fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() { notificationShadeDepthController.panelPullDownMinFraction = 0.5f - notificationShadeDepthController.onPanelExpansionChanged(0.5f /* expansion */, - true /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 0.5f, expanded = true, tracking = true + ) assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f) - notificationShadeDepthController.onPanelExpansionChanged(0.75f /* expansion */, - true /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 0.75f, expanded = true, tracking = true + ) assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0.5f) - notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */, - true /* tracking */) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = true + ) assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(1f) } @@ -196,7 +206,9 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { fun setQsPanelExpansion_appliesBlur() { statusBarState = StatusBarState.KEYGUARD notificationShadeDepthController.qsPanelExpansion = 1f - notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = false + ) notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false)) } @@ -205,7 +217,9 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { fun setQsPanelExpansion_easing() { statusBarState = StatusBarState.KEYGUARD notificationShadeDepthController.qsPanelExpansion = 0.25f - notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = false + ) notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(wallpaperController).setNotificationShadeZoom( eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f))) @@ -261,7 +275,9 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Test fun updateBlurCallback_setsBlur_whenExpanded() { - notificationShadeDepthController.onPanelExpansionChanged(1f, false) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = false + ) `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat()) notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false)) @@ -269,7 +285,9 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Test fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() { - notificationShadeDepthController.onPanelExpansionChanged(1f, false) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = false + ) `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat()) notificationShadeDepthController.blursDisabledForAppLaunch = true notificationShadeDepthController.updateBlurCallback.doFrame(0) @@ -300,7 +318,9 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { // Brightness mirror is fully visible `when`(brightnessSpring.ratio).thenReturn(1f) // And shade is blurred - notificationShadeDepthController.onPanelExpansionChanged(1f, false) + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = false + ) `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat()) notificationShadeDepthController.updateBlurCallback.doFrame(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 7fb7b8667a1b..cf58c63e3d26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -36,6 +36,7 @@ import android.widget.LinearLayout; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.DynamicChildBindController; @@ -75,6 +76,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { @Spy private FakeListContainer mListContainer = new FakeListContainer(); // Dependency mocks: + @Mock private FeatureFlags mFeatureFlags; @Mock private NotificationEntryManager mEntryManager; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationGroupManagerLegacy mGroupManager; @@ -101,10 +103,14 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true); when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true); + when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); + when(mFeatureFlags.checkLegacyPipelineEnabled()).thenReturn(true); + mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); mViewHierarchyManager = new NotificationViewHierarchyManager(mContext, - mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager, + mHandler, mFeatureFlags, mLockscreenUserManager, mGroupManager, + mVisualStabilityManager, mock(StatusBarStateControllerImpl.class), mEntryManager, mock(KeyguardBypassController.class), Optional.of(mock(Bubbles.class)), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java new file mode 100644 index 000000000000..ce11d6a62a8c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java @@ -0,0 +1,174 @@ +/* + * 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.systemui.statusbar; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.mock; + +import android.app.Notification; +import android.app.RemoteInputHistoryItem; +import android.net.Uri; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; + +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) +@TestableLooper.RunWithLooper +public class RemoteInputNotificationRebuilderTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + @Mock + private ExpandableNotificationRow mRow; + + private RemoteInputNotificationRebuilder mRebuilder; + private NotificationEntry mEntry; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mRebuilder = new RemoteInputNotificationRebuilder(mContext); + mEntry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setNotification(new Notification()) + .setUser(UserHandle.CURRENT) + .build(); + mEntry.setRow(mRow); + } + + @Test + public void testRebuildWithRemoteInput_noExistingInput_image() { + Uri uri = mock(Uri.class); + String mimeType = "image/jpeg"; + String text = "image inserted"; + StatusBarNotification newSbn = + mRebuilder.rebuildWithRemoteInputInserted( + mEntry, text, false, mimeType, uri); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(1, messages.length); + assertEquals(text, messages[0].getText()); + assertEquals(mimeType, messages[0].getMimeType()); + assertEquals(uri, messages[0].getUri()); + } + + @Test + public void testRebuildWithRemoteInput_noExistingInputNoSpinner() { + StatusBarNotification newSbn = + mRebuilder.rebuildWithRemoteInputInserted( + mEntry, "A Reply", false, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(1, messages.length); + assertEquals("A Reply", messages[0].getText()); + assertFalse(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); + assertTrue(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); + } + + @Test + public void testRebuildWithRemoteInput_noExistingInputWithSpinner() { + StatusBarNotification newSbn = + mRebuilder.rebuildWithRemoteInputInserted( + mEntry, "A Reply", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(1, messages.length); + assertEquals("A Reply", messages[0].getText()); + assertTrue(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); + assertTrue(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); + } + + @Test + public void testRebuildWithRemoteInput_withExistingInput() { + // Setup a notification entry with 1 remote input. + StatusBarNotification newSbn = + mRebuilder.rebuildWithRemoteInputInserted( + mEntry, "A Reply", false, null, null); + NotificationEntry entry = new NotificationEntryBuilder() + .setSbn(newSbn) + .build(); + + // Try rebuilding to add another reply. + newSbn = mRebuilder.rebuildWithRemoteInputInserted( + entry, "Reply 2", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(2, messages.length); + assertEquals("Reply 2", messages[0].getText()); + assertEquals("A Reply", messages[1].getText()); + } + + @Test + public void testRebuildWithRemoteInput_withExistingInput_image() { + // Setup a notification entry with 1 remote input. + Uri uri = mock(Uri.class); + String mimeType = "image/jpeg"; + String text = "image inserted"; + StatusBarNotification newSbn = + mRebuilder.rebuildWithRemoteInputInserted( + mEntry, text, false, mimeType, uri); + NotificationEntry entry = new NotificationEntryBuilder() + .setSbn(newSbn) + .build(); + + // Try rebuilding to add another reply. + newSbn = mRebuilder.rebuildWithRemoteInputInserted( + entry, "Reply 2", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(2, messages.length); + assertEquals("Reply 2", messages[0].getText()); + assertEquals(text, messages[1].getText()); + assertEquals(mimeType, messages[1].getMimeType()); + assertEquals(uri, messages[1].getUri()); + } + + @Test + public void testRebuildNotificationForCanceledSmartReplies() { + // Try rebuilding to remove spinner and hide buttons. + StatusBarNotification newSbn = + mRebuilder.rebuildForCanceledSmartReplies(mEntry); + assertFalse(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); + assertTrue(newSbn.getNotification().extras + .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java index 837d71f8d74f..99c965a9e57f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java @@ -39,6 +39,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -86,14 +87,20 @@ public class SmartReplyControllerTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager); - mSmartReplyController = new SmartReplyController(mNotificationEntryManager, - mIStatusBarService, mClickNotifier); + mSmartReplyController = new SmartReplyController( + mock(DumpManager.class), + mNotificationEntryManager, + mIStatusBarService, + mClickNotifier); mDependency.injectTestDependency(SmartReplyController.class, mSmartReplyController); mRemoteInputManager = new NotificationRemoteInputManager(mContext, + mock(FeatureFlags.class), mock(NotificationLockscreenUserManager.class), mSmartReplyController, - mNotificationEntryManager, () -> Optional.of(mock(StatusBar.class)), + mNotificationEntryManager, + new RemoteInputNotificationRebuilder(mContext), + () -> Optional.of(mock(StatusBar.class)), mStatusBarStateController, Handler.createAsync(Looper.myLooper()), mRemoteInputUriController, 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 39d794dc0bd9..f08a74ab1316 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 @@ -35,6 +35,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -50,6 +51,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.Nullable; import android.app.Notification; +import android.os.Handler; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; @@ -60,6 +62,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -76,6 +79,7 @@ import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoa import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; +import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; @@ -106,6 +110,7 @@ public class NotifCollectionTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Mock private NotifCollectionLogger mLogger; @Mock private LogBufferEulogizer mEulogizer; + @Mock private Handler mMainHandler; @Mock private GroupCoalescer mGroupCoalescer; @Spy private RecordingCollectionListener mCollectionListener; @@ -151,6 +156,7 @@ public class NotifCollectionTest extends SysuiTestCase { mClock, mFeatureFlags, mLogger, + mMainHandler, mEulogizer, mock(DumpManager.class)); mCollection.attach(mGroupCoalescer); @@ -1321,6 +1327,78 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt()); } + private Runnable getInternalNotifUpdateRunnable(StatusBarNotification sbn) { + InternalNotifUpdater updater = mCollection.getInternalNotifUpdater("Test"); + updater.onInternalNotificationUpdate(sbn, "reason"); + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mMainHandler).post(runnableCaptor.capture()); + return runnableCaptor.getValue(); + } + + @Test + public void testGetInternalNotifUpdaterPostsToMainHandler() { + InternalNotifUpdater updater = mCollection.getInternalNotifUpdater("Test"); + updater.onInternalNotificationUpdate(mock(StatusBarNotification.class), "reason"); + verify(mMainHandler).post(any()); + } + + @Test + public void testSecondPostCallsUpdateWithTrue() { + // GIVEN a pipeline with one notification + NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notifEvent.key); + + // KNOWING that it already called listener methods once + verify(mCollectionListener).onEntryAdded(eq(entry)); + verify(mCollectionListener).onRankingApplied(); + + // WHEN we update the notification via the system + mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + + // THEN entry updated gets called, added does not, and ranking is called again + verify(mCollectionListener).onEntryUpdated(eq(entry)); + verify(mCollectionListener).onEntryUpdated(eq(entry), eq(true)); + verify(mCollectionListener).onEntryAdded((entry)); + verify(mCollectionListener, times(2)).onRankingApplied(); + } + + @Test + public void testInternalNotifUpdaterCallsUpdate() { + // GIVEN a pipeline with one notification + NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notifEvent.key); + + // KNOWING that it will call listener methods once + verify(mCollectionListener).onEntryAdded(eq(entry)); + verify(mCollectionListener).onRankingApplied(); + + // WHEN we update that notification internally + StatusBarNotification sbn = notifEvent.sbn; + getInternalNotifUpdateRunnable(sbn).run(); + + // THEN only entry updated gets called a second time + verify(mCollectionListener).onEntryAdded(eq(entry)); + verify(mCollectionListener).onRankingApplied(); + verify(mCollectionListener).onEntryUpdated(eq(entry)); + verify(mCollectionListener).onEntryUpdated(eq(entry), eq(false)); + } + + @Test + public void testInternalNotifUpdaterIgnoresNew() { + // GIVEN a pipeline without any notifications + StatusBarNotification sbn = buildNotif(TEST_PACKAGE, 47, "myTag").build().getSbn(); + + // WHEN we internally update an unknown notification + getInternalNotifUpdateRunnable(sbn).run(); + + // THEN only entry updated gets called a second time + verify(mCollectionListener, never()).onEntryAdded(any()); + verify(mCollectionListener, never()).onRankingUpdate(any()); + verify(mCollectionListener, never()).onRankingApplied(); + verify(mCollectionListener, never()).onEntryUpdated(any()); + verify(mCollectionListener, never()).onEntryUpdated(any(), anyBoolean()); + } + private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) { return new NotificationEntryBuilder() .setPkg(pkg) @@ -1371,6 +1449,11 @@ public class NotifCollectionTest extends SysuiTestCase { } @Override + public void onEntryUpdated(NotificationEntry entry, boolean fromSystem) { + onEntryUpdated(entry); + } + + @Override public void onEntryRemoved(NotificationEntry entry, int reason) { } @@ -1405,25 +1488,26 @@ public class NotifCollectionTest extends SysuiTestCase { mName = name; } + @NonNull @Override public String getName() { return mName; } @Override - public void setCallback(OnEndLifetimeExtensionCallback callback) { + public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) { this.callback = callback; } @Override public boolean shouldExtendLifetime( - NotificationEntry entry, + @NonNull NotificationEntry entry, @CancellationReason int reason) { return shouldExtendLifetime; } @Override - public void cancelLifetimeExtension(NotificationEntry entry) { + public void cancelLifetimeExtension(@NonNull NotificationEntry entry) { if (onCancelLifetimeExtension != null) { onCancelLifetimeExtension.run(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 3378003b0d44..190c3521e83c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static java.util.Collections.singletonList; @@ -42,6 +43,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -54,6 +56,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; @@ -78,6 +81,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @SmallTest @@ -608,6 +612,30 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + public void testNotifSectionsChildrenUpdated() { + AtomicBoolean validChildren = new AtomicBoolean(false); + final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1) { + @Nullable + @Override + public void onEntriesUpdated(List<ListEntry> entries) { + super.onEntriesUpdated(entries); + validChildren.set(entries.size() == 2); + } + }); + mListBuilder.setSectioners(Arrays.asList(pkg1Sectioner)); + + addNotif(0, PACKAGE_4); + addNotif(1, PACKAGE_1); + addNotif(2, PACKAGE_1); + addNotif(3, PACKAGE_3); + + dispatchBuild(); + + verify(pkg1Sectioner, times(1)).onEntriesUpdated(any()); + assertTrue(validChildren.get()); + } + + @Test public void testNotifSections() { // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide // notifs based on package name @@ -820,11 +848,13 @@ public class ShadeListBuilderTest extends SysuiTestCase { NotifPromoter idPromoter = new IdPromoter(4); NotifSectioner section = new PackageSectioner(PACKAGE_1); NotifComparator hypeComparator = new HypeComparator(PACKAGE_2); + Invalidator preRenderInvalidator = new Invalidator("PreRenderInvalidator") {}; mListBuilder.addPreGroupFilter(packageFilter); mListBuilder.addPromoter(idPromoter); mListBuilder.setSectioners(singletonList(section)); mListBuilder.setComparators(singletonList(hypeComparator)); + mListBuilder.addPreRenderInvalidator(preRenderInvalidator); // GIVEN a set of random notifs addNotif(0, PACKAGE_1); @@ -849,6 +879,10 @@ public class ShadeListBuilderTest extends SysuiTestCase { clearInvocations(mOnRenderListListener); hypeComparator.invalidateList(); verify(mOnRenderListListener).onRenderList(anyList()); + + clearInvocations(mOnRenderListListener); + preRenderInvalidator.invalidateList(); + verify(mOnRenderListListener).onRenderList(anyList()); } @Test @@ -1633,7 +1667,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { private final String mPackage; PackageSectioner(String pkg) { - super("PackageSection_" + pkg); + super("PackageSection_" + pkg, 0); mPackage = pkg; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt new file mode 100644 index 000000000000..0cba07033c63 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt @@ -0,0 +1,117 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager +import com.android.systemui.statusbar.notification.row.NotificationGuts +import com.android.systemui.util.mockito.argumentCaptor +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.mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class GutsCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: GutsCoordinator + private lateinit var notifLifetimeExtender: NotifLifetimeExtender + private lateinit var notifGutsViewListener: NotifGutsViewListener + + private lateinit var entry1: NotificationEntry + private lateinit var entry2: NotificationEntry + + @Mock private lateinit var notifGutsViewManager: NotifGutsViewManager + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var logger: GutsCoordinatorLogger + @Mock private lateinit var lifetimeExtenderCallback: OnEndLifetimeExtensionCallback + + @Before + fun setUp() { + initMocks(this) + coordinator = GutsCoordinator(notifGutsViewManager, logger, dumpManager) + coordinator.attach(pipeline) + notifLifetimeExtender = argumentCaptor<NotifLifetimeExtender>().let { + verify(pipeline).addNotificationLifetimeExtender(it.capture()) + it.value!! + } + notifGutsViewListener = argumentCaptor<NotifGutsViewListener>().let { + verify(notifGutsViewManager).setGutsListener(it.capture()) + it.value!! + } + notifLifetimeExtender.setCallback(lifetimeExtenderCallback) + entry1 = NotificationEntryBuilder().setId(1).build() + entry2 = NotificationEntryBuilder().setId(2).build() + } + + @Test + fun testSimpleLifetimeExtension() { + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsClose(entry1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + } + + @Test + fun testDoubleOpenLifetimeExtension() { + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsClose(entry1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + } + + @Test + fun testTwoEntryLifetimeExtension() { + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entry2, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue() + notifGutsViewListener.onGutsClose(entry1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue() + notifGutsViewListener.onGutsClose(entry2) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry2) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index 1031d6befc36..8f241a37c5ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -21,17 +21,22 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Notification; +import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -39,6 +44,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; +import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import org.junit.Before; import org.junit.Test; @@ -46,8 +52,11 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.Arrays; + @SmallTest @RunWith(AndroidTestingRunner.class) public class RankingCoordinatorTest extends SysuiTestCase { @@ -56,7 +65,8 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private NotifPipeline mNotifPipeline; @Mock private NodeController mAlertingHeaderController; - @Mock private NodeController mSilentHeaderController; + @Mock private NodeController mSilentNodeController; + @Mock private SectionHeaderController mSilentHeaderController; @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor; @@ -72,7 +82,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); RankingCoordinator rankingCoordinator = new RankingCoordinator( mStatusBarStateController, mHighPriorityProvider, mAlertingHeaderController, - mSilentHeaderController); + mSilentHeaderController, mSilentNodeController); mEntry = new NotificationEntryBuilder().build(); rankingCoordinator.attach(mNotifPipeline); @@ -85,6 +95,28 @@ public class RankingCoordinatorTest extends SysuiTestCase { } @Test + public void testSilentHeaderClearableChildrenUpdate() { + StatusBarNotification sbn = Mockito.mock(StatusBarNotification.class); + Mockito.doReturn("key").when(sbn).getKey(); + Mockito.doReturn(Mockito.mock(Notification.class)).when(sbn).getNotification(); + NotificationEntry entry = new NotificationEntryBuilder().setSbn(sbn).build(); + ListEntry listEntry = new ListEntry("key", 0L) { + @Nullable + @Override + public NotificationEntry getRepresentativeEntry() { + return entry; + } + }; + Mockito.doReturn(true).when(sbn).isClearable(); + mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); + verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); + + Mockito.doReturn(false).when(sbn).isClearable(); + mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); + verify(mSilentHeaderController).setClearSectionEnabled(eq(false)); + } + + @Test public void testUnfilteredState() { // GIVEN no suppressed visual effects + app not suspended mEntry.setRanking(getRankingForUnfilteredNotif().build()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt new file mode 100644 index 000000000000..0ce6ada51f23 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt @@ -0,0 +1,145 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import android.os.Handler +import android.service.notification.StatusBarNotification +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputListener +import com.android.systemui.statusbar.RemoteInputNotificationRebuilder +import com.android.systemui.statusbar.SmartReplyController +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.withArgCaptor +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.`when` +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class RemoteInputCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: RemoteInputCoordinator + private lateinit var listener: RemoteInputListener + private lateinit var collectionListener: NotifCollectionListener + + private lateinit var entry1: NotificationEntry + private lateinit var entry2: NotificationEntry + + @Mock private lateinit var lifetimeExtensionCallback: OnEndLifetimeExtensionCallback + @Mock private lateinit var rebuilder: RemoteInputNotificationRebuilder + @Mock private lateinit var remoteInputManager: NotificationRemoteInputManager + @Mock private lateinit var mainHandler: Handler + @Mock private lateinit var smartReplyController: SmartReplyController + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var notifUpdater: InternalNotifUpdater + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var sbn: StatusBarNotification + + @Before + fun setUp() { + initMocks(this) + coordinator = RemoteInputCoordinator( + dumpManager, + rebuilder, + remoteInputManager, + mainHandler, + smartReplyController + ) + `when`(pipeline.addNotificationLifetimeExtender(any())).thenAnswer { + (it.arguments[0] as NotifLifetimeExtender).setCallback(lifetimeExtensionCallback) + } + `when`(pipeline.getInternalNotifUpdater(any())).thenReturn(notifUpdater) + coordinator.attach(pipeline) + listener = withArgCaptor { + verify(remoteInputManager).setRemoteInputListener(capture()) + } + collectionListener = withArgCaptor { + verify(pipeline).addCollectionListener(capture()) + } + entry1 = NotificationEntryBuilder().setId(1).build() + entry2 = NotificationEntryBuilder().setId(2).build() + `when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn) + `when`(rebuilder.rebuildForRemoteInputReply(any())).thenReturn(sbn) + `when`(rebuilder.rebuildForSendingSmartReply(any(), any())).thenReturn(sbn) + } + + val remoteInputActiveExtender get() = coordinator.mRemoteInputActiveExtender + val remoteInputHistoryExtender get() = coordinator.mRemoteInputHistoryExtender + val smartReplyHistoryExtender get() = coordinator.mSmartReplyHistoryExtender + + @Test + fun testRemoteInputActive() { + `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true) + assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isFalse() + } + + @Test + fun testRemoteInputHistory() { + `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry1)).thenReturn(true) + assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isTrue() + } + + @Test + fun testSmartReplyHistory() { + `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry1)).thenReturn(true) + assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isTrue() + } + + @Test + fun testNotificationWithRemoteInputActiveIsRemovedOnCollapse() { + `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true) + assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse() + + // Nothing should happen on panel collapse before we start extending the lifetime + listener.onPanelCollapsed() + assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse() + verify(lifetimeExtensionCallback, never()).onEndLifetimeExtension(any(), any()) + + // Start extending lifetime & validate that the extension is ended + assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isTrue() + listener.onPanelCollapsed() + verify(lifetimeExtensionCallback).onEndLifetimeExtension(remoteInputActiveExtender, entry1) + assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt new file mode 100644 index 000000000000..5fd4174af164 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt @@ -0,0 +1,209 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import android.os.UserHandle +import android.service.notification.StatusBarNotification +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.notification.DynamicPrivacyController +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.mockito.mock +import org.junit.Test +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever + +@SmallTest +class SensitiveContentCoordinatorTest : SysuiTestCase() { + + val dynamicPrivacyController: DynamicPrivacyController = mock() + val lockscreenUserManager: NotificationLockscreenUserManager = mock() + val pipeline: NotifPipeline = mock() + + val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule + .provideCoordinator(dynamicPrivacyController, lockscreenUserManager) + + @Test + fun onDynamicPrivacyChanged_invokeInvalidationListener() { + coordinator.attach(pipeline) + val invalidator = withArgCaptor<Invalidator> { + verify(pipeline).addPreRenderInvalidator(capture()) + } + val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> { + verify(dynamicPrivacyController).addListener(capture()) + } + + val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() + invalidator.setInvalidationListener(invalidationListener) + + dynamicPrivacyListener.onDynamicPrivacyChanged() + + verify(invalidationListener).onPluggableInvalidated(invalidator) + } + + @Test + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } + + @Test + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } + + @Test + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } + + @Test + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + + val entry = fakeNotification(2, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry { + val mockUserHandle = mock<UserHandle>().apply { + whenever(identifier).thenReturn(notifUserId) + } + val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply { + whenever(user).thenReturn(mockUserHandle) + } + val mockEntry = mock<NotificationEntry>().apply { + whenever(sbn).thenReturn(mockSbn) + } + whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction) + whenever(mockEntry.rowExists()).thenReturn(true) + return object : ListEntry("key", 0) { + override fun getRepresentativeEntry(): NotificationEntry = mockEntry + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt new file mode 100644 index 000000000000..5915cd7823f0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt @@ -0,0 +1,106 @@ +/* + * 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.systemui.statusbar.notification.collection.coordinator + +import android.service.notification.NotificationListenerService.REASON_APP_CANCEL +import android.service.notification.NotificationListenerService.REASON_CANCEL +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.util.mockito.argumentCaptor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class ShadeEventCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: ShadeEventCoordinator + private lateinit var notifCollectionListener: NotifCollectionListener + private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener + + private lateinit var entry1: NotificationEntry + private lateinit var entry2: NotificationEntry + + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var logger: ShadeEventCoordinatorLogger + @Mock private lateinit var notifRemovedByUserCallback: Runnable + @Mock private lateinit var shadeEmptiedCallback: Runnable + + @Before + fun setUp() { + initMocks(this) + coordinator = ShadeEventCoordinator(logger) + coordinator.attach(pipeline) + notifCollectionListener = argumentCaptor<NotifCollectionListener>().let { + verify(pipeline).addCollectionListener(it.capture()) + it.value!! + } + onBeforeRenderListListener = argumentCaptor<OnBeforeRenderListListener>().let { + verify(pipeline).addOnBeforeRenderListListener(it.capture()) + it.value!! + } + coordinator.setNotifRemovedByUserCallback(notifRemovedByUserCallback) + coordinator.setShadeEmptiedCallback(shadeEmptiedCallback) + entry1 = NotificationEntryBuilder().setId(1).build() + entry2 = NotificationEntryBuilder().setId(2).build() + } + + @Test + fun testUserCancelLastNotification() { + notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL) + verify(shadeEmptiedCallback, never()).run() + verify(notifRemovedByUserCallback, never()).run() + onBeforeRenderListListener.onBeforeRenderList(listOf()) + verify(shadeEmptiedCallback).run() + verify(notifRemovedByUserCallback).run() + } + + @Test + fun testAppCancelLastNotification() { + notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL) + onBeforeRenderListListener.onBeforeRenderList(listOf()) + verify(shadeEmptiedCallback).run() + verify(notifRemovedByUserCallback, never()).run() + } + + @Test + fun testUserCancelOneOfTwoNotifications() { + notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry2)) + verify(shadeEmptiedCallback, never()).run() + verify(notifRemovedByUserCallback).run() + } + + @Test + fun testAppCancelOneOfTwoNotifications() { + notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry2)) + verify(shadeEmptiedCallback, never()).run() + verify(notifRemovedByUserCallback, never()).run() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt new file mode 100644 index 000000000000..37ad8357aa95 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt @@ -0,0 +1,230 @@ +/* + * 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.systemui.statusbar.notification.collection.notifcollection + +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.util.mockito.withArgCaptor +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.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.anyLong +import org.mockito.Mockito.eq +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks +import java.util.function.Consumer +import java.util.function.Predicate + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { + private lateinit var extender: TestableSelfTrackingLifetimeExtender + + private lateinit var entry1: NotificationEntry + private lateinit var entry2: NotificationEntry + + @Mock + private lateinit var callback: OnEndLifetimeExtensionCallback + @Mock + private lateinit var mainHandler: Handler + @Mock + private lateinit var shouldExtend: Predicate<NotificationEntry> + @Mock + private lateinit var onStarted: Consumer<NotificationEntry> + @Mock + private lateinit var onCanceled: Consumer<NotificationEntry> + + @Before + fun setUp() { + initMocks(this) + extender = TestableSelfTrackingLifetimeExtender() + extender.setCallback(callback) + entry1 = NotificationEntryBuilder().setId(1).build() + entry2 = NotificationEntryBuilder().setId(2).build() + } + + @Test + fun testName() { + assertThat(extender.name).isEqualTo("Testable") + } + + @Test + fun testNoExtend() { + `when`(shouldExtend.test(entry1)).thenReturn(false) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(extender.isExtending(entry1.key)).isFalse() + verify(onStarted, never()).accept(entry1) + verify(onCanceled, never()).accept(entry1) + } + + @Test + fun testExtendThenCancelForRepost() { + `when`(shouldExtend.test(entry1)).thenReturn(true) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + verify(onStarted).accept(entry1) + verify(onCanceled, never()).accept(entry1) + assertThat(extender.isExtending(entry1.key)).isTrue() + extender.cancelLifetimeExtension(entry1) + verify(onCanceled).accept(entry1) + } + + @Test + fun testExtendThenCancel_thenEndDoesNothing() { + testExtendThenCancelForRepost() + assertThat(extender.isExtending(entry1.key)).isFalse() + + extender.endLifetimeExtension(entry1.key) + extender.endLifetimeExtensionAfterDelay(entry1.key, 1000) + verify(callback, never()).onEndLifetimeExtension(any(), any()) + verify(mainHandler, never()).postDelayed(any(), anyLong()) + } + + @Test + fun testExtendThenEnd() { + `when`(shouldExtend.test(entry1)).thenReturn(true) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + verify(onStarted).accept(entry1) + assertThat(extender.isExtending(entry1.key)).isTrue() + extender.endLifetimeExtension(entry1.key) + verify(callback).onEndLifetimeExtension(extender, entry1) + verify(onCanceled, never()).accept(entry1) + } + + @Test + fun testExtendThenEndAfterDelay() { + `when`(shouldExtend.test(entry1)).thenReturn(true) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + verify(onStarted).accept(entry1) + assertThat(extender.isExtending(entry1.key)).isTrue() + + // Call the method and capture the posted runnable + extender.endLifetimeExtensionAfterDelay(entry1.key, 1234) + val runnable = withArgCaptor<Runnable> { + verify(mainHandler).postDelayed(capture(), eq(1234.toLong())) + } + assertThat(extender.isExtending(entry1.key)).isTrue() + verify(callback, never()).onEndLifetimeExtension(any(), any()) + + // now run the posted runnable and ensure it works as expected + runnable.run() + verify(callback).onEndLifetimeExtension(extender, entry1) + assertThat(extender.isExtending(entry1.key)).isFalse() + verify(onCanceled, never()).accept(entry1) + } + + @Test + fun testExtendThenEndAll() { + `when`(shouldExtend.test(entry1)).thenReturn(true) + `when`(shouldExtend.test(entry2)).thenReturn(true) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + verify(onStarted).accept(entry1) + assertThat(extender.isExtending(entry1.key)).isTrue() + assertThat(extender.isExtending(entry2.key)).isFalse() + assertThat(extender.shouldExtendLifetime(entry2, 0)).isTrue() + verify(onStarted).accept(entry2) + assertThat(extender.isExtending(entry1.key)).isTrue() + assertThat(extender.isExtending(entry2.key)).isTrue() + extender.endAllLifetimeExtensions() + verify(callback).onEndLifetimeExtension(extender, entry1) + verify(callback).onEndLifetimeExtension(extender, entry2) + verify(onCanceled, never()).accept(entry1) + verify(onCanceled, never()).accept(entry2) + } + + @Test + fun testExtendWithinEndCanReExtend() { + `when`(shouldExtend.test(entry1)).thenReturn(true) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + verify(onStarted, times(1)).accept(entry1) + + `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + } + extender.endLifetimeExtension(entry1.key) + verify(onStarted, times(2)).accept(entry1) + assertThat(extender.isExtending(entry1.key)).isTrue() + } + + @Test + fun testExtendWithinEndCanNotReExtend() { + `when`(shouldExtend.test(entry1)).thenReturn(true, false) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + verify(onStarted, times(1)).accept(entry1) + + `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { + assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + } + extender.endLifetimeExtension(entry1.key) + verify(onStarted, times(1)).accept(entry1) + assertThat(extender.isExtending(entry1.key)).isFalse() + } + + @Test + fun testExtendWithinEndAllCanReExtend() { + `when`(shouldExtend.test(entry1)).thenReturn(true) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + verify(onStarted, times(1)).accept(entry1) + + `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + } + extender.endAllLifetimeExtensions() + verify(onStarted, times(2)).accept(entry1) + assertThat(extender.isExtending(entry1.key)).isTrue() + } + + @Test + fun testExtendWithinEndAllCanNotReExtend() { + `when`(shouldExtend.test(entry1)).thenReturn(true, false) + assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + verify(onStarted, times(1)).accept(entry1) + + `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { + assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + } + extender.endAllLifetimeExtensions() + verify(onStarted, times(1)).accept(entry1) + assertThat(extender.isExtending(entry1.key)).isFalse() + } + + inner class TestableSelfTrackingLifetimeExtender(debug: Boolean = false) : + SelfTrackingLifetimeExtender("Test", "Testable", debug, mainHandler) { + + override fun queryShouldExtendLifetime(entry: NotificationEntry) = + shouldExtend.test(entry) + + override fun onStartedLifetimeExtension(entry: NotificationEntry) { + onStarted.accept(entry) + } + + override fun onCanceledLifetimeExtension(entry: NotificationEntry) { + onCanceled.accept(entry) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index 2e676bbe6541..ed48452eccc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -26,6 +26,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.getAttachState import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING +import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE +import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT +import com.android.systemui.statusbar.notification.stack.PriorityBucket import com.android.systemui.util.mockito.any import org.junit.Before import org.junit.Test @@ -45,11 +49,15 @@ class NodeSpecBuilderTest : SysuiTestCase() { private var headerController1: NodeController = buildFakeController("header1") private var headerController2: NodeController = buildFakeController("header2") - private val section0 = buildSection(0, headerController0) - private val section0NoHeader = buildSection(0, null) - private val section1 = buildSection(1, headerController1) - private val section1NoHeader = buildSection(1, null) - private val section2 = buildSection(2, headerController2) + private val section0Bucket = BUCKET_PEOPLE + private val section1Bucket = BUCKET_ALERTING + private val section2Bucket = BUCKET_SILENT + + private val section0 = buildSection(0, section0Bucket, headerController0) + private val section0NoHeader = buildSection(0, section0Bucket, null) + private val section1 = buildSection(1, section1Bucket, headerController1) + private val section1NoHeader = buildSection(1, section1Bucket, null) + private val section2 = buildSection(2, section2Bucket, headerController2) private val fakeViewBarn = FakeViewBarn() @@ -297,8 +305,12 @@ private fun buildFakeController(name: String): NodeController { return controller } -private fun buildSection(index: Int, nodeController: NodeController?): NotifSection { - return NotifSection(object : NotifSectioner("Section $index") { +private fun buildSection( + index: Int, + @PriorityBucket bucket: Int, + nodeController: NodeController? +): NotifSection { + return NotifSection(object : NotifSectioner("Section $index (bucket=$bucket)", bucket) { override fun isInSection(entry: ListEntry?): Boolean { throw NotImplementedError("This should never be called") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 42aecfdc11bd..c5d1e3acb2b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -255,6 +255,22 @@ public class NotificationTestHelper { } /** + * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. + */ + public ExpandableNotificationRow createShortcutBubble(String shortcutId) + throws Exception { + Notification n = createNotification(false /* isGroupSummary */, + null /* groupKey */, makeShortcutBubbleMetadata(shortcutId)); + n.flags |= FLAG_BUBBLE; + ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE, + 0 /* extraInflationFlags */, IMPORTANCE_HIGH); + modifyRanking(row.getEntry()) + .setCanBubble(true) + .build(); + return row; + } + + /** * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble and is part * of a group of notifications. */ @@ -506,6 +522,12 @@ public class NotificationTestHelper { .build(); } + private BubbleMetadata makeShortcutBubbleMetadata(String shortcutId) { + return new BubbleMetadata.Builder(shortcutId) + .setDesiredHeight(314) + .build(); + } + private static class MockSmartReplyInflater implements SmartReplyStateInflater { @Override public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index c1d2ea88a1b1..f11f8c476433 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -18,11 +18,11 @@ package com.android.systemui.statusbar.notification.stack; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.google.common.truth.Truth.assertThat; @@ -64,7 +64,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -608,7 +607,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { } } - private View mockNotification(int bucket, boolean isGone) { + private View mockNotification(@PriorityBucket int bucket, boolean isGone) { ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(notifRow.getVisibility()).thenReturn(View.VISIBLE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt index f3136c7be967..bf8cc37697b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt @@ -40,7 +40,7 @@ class CollapsedStatusBarFragmentLoggerTest : SysuiTestCase() { private val logger = CollapsedStatusBarFragmentLogger(buffer, disableFlagsLogger) @Test - fun logToBuffer_bufferHasStates() { + fun logDisableFlagChange_bufferHasStates() { val state = DisableFlagsLogger.DisableState(0, 1) logger.logDisableFlagChange(state, state) @@ -48,7 +48,9 @@ class CollapsedStatusBarFragmentLoggerTest : SysuiTestCase() { val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) val actualString = stringWriter.toString() - val expectedLogString = disableFlagsLogger.getDisableFlagsString(state, state) + val expectedLogString = disableFlagsLogger.getDisableFlagsString( + old = null, new = state, newAfterLocalModification = state + ) assertThat(actualString).contains(expectedLogString) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index ead32910f943..23378ab6e80a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -90,6 +90,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.media.KeyguardMediaController; @@ -121,6 +122,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -302,6 +304,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private DumpManager mDumpManager; @Mock private NotificationsQSContainerController mNotificationsQSContainerController; + @Mock + private FeatureFlags mFeatureFlags; private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; @@ -444,8 +448,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mSplitShadeHeaderController, mUnlockedScreenOffAnimationController, mLockscreenGestureLogger, + new PanelExpansionStateManager(), mNotificationRemoteInputManager, - mControlsComponent); + mControlsComponent, + mFeatureFlags); mNotificationPanelViewController.initDependencies( mStatusBar, () -> {}, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index 6e9bb2d30ed3..4bf8821d1185 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.tuner.TunerService; @@ -90,7 +91,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; @Mock private NotificationShadeDepthController mNotificationShadeDepthController; - @Mock private StatusBarWindowView mStatusBarWindowView; + @Mock private StatusBarWindowController mStatusBarWindowController; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -135,7 +136,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mNotificationShadeDepthController, mView, mNotificationPanelViewController, - mStatusBarWindowView, + new PanelExpansionStateManager(), + mStatusBarWindowController, mNotificationStackScrollLayoutController, mStatusBarKeyguardViewManager, mLockIconViewController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index fe3490399e81..300860ca0a49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -54,25 +54,6 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test - fun panelExpansionChanged_expansionChangeListenerNotified() { - val listener = TestExpansionChangedListener() - view.setExpansionChangedListeners(listOf(listener)) - val fraction = 0.4f - val isExpanded = true - - view.panelExpansionChanged(fraction, isExpanded) - - assertThat(listener.fraction).isEqualTo(fraction) - assertThat(listener.isExpanded).isEqualTo(isExpanded) - } - - @Test - fun panelExpansionChanged_noListeners_noCrash() { - view.panelExpansionChanged(1f, false) - // No assert needed, just testing no crash - } - - @Test fun panelStateChanged_toStateOpening_listenerNotified() { val listener = TestStateChangedListener() view.setPanelStateChangeListener(listener) @@ -145,17 +126,6 @@ class PhoneStatusBarViewTest : SysuiTestCase() { // No assert needed, just testing no crash } - private class TestExpansionChangedListener - : StatusBar.ExpansionChangedListener { - var fraction: Float = 0f - var isExpanded: Boolean = false - - override fun onExpansionChanged(expansion: Float, expanded: Boolean) { - this.fraction = expansion - this.isExpanded = expanded - } - } - private class TestStateChangedListener : PanelBar.PanelStateChangeListener { var state: Int = 0 override fun onStateChanged(state: Int) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 6849dab1a19a..42f22063110e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -55,6 +55,7 @@ import com.android.systemui.DejankUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.scrim.ScrimView; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -112,6 +113,10 @@ public class ScrimControllerTest extends SysuiTestCase { private ConfigurationController mConfigurationController; @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The + // event-dispatch-on-registration pattern caused some of these unit tests to fail.) + @Mock + private PanelExpansionStateManager mPanelExpansionStateManager; private static class AnimatorListener implements Animator.AnimatorListener { @@ -224,7 +229,8 @@ public class ScrimControllerTest extends SysuiTestCase { mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()), - mUnlockedScreenOffAnimationController); + mUnlockedScreenOffAnimationController, + mPanelExpansionStateManager); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt index a9e8164e8b87..0cf0bd300419 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt @@ -1,8 +1,7 @@ package com.android.systemui.statusbar.phone -import android.content.Context -import android.content.res.Resources import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner import android.view.View import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -15,14 +14,16 @@ import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever @SmallTest +@RunWith(AndroidTestingRunner::class) class SplitShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var view: View @@ -33,8 +34,7 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var batteryMeterView: BatteryMeterView @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController - @Mock private lateinit var resources: Resources - @Mock private lateinit var context: Context + @JvmField @Rule val mockitoRule = MockitoJUnit.rule() var viewVisibility = View.GONE @@ -45,8 +45,8 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() { whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon)) .thenReturn(batteryMeterView) whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons) + whenever(view.context).thenReturn(context) whenever(statusIcons.context).thenReturn(context) - whenever(context.resources).thenReturn(resources) whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any())) .thenReturn(qsCarrierGroupControllerBuilder) whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController) @@ -55,6 +55,7 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() { null } whenever(view.visibility).thenAnswer { _ -> viewVisibility } + whenever(featureFlags.useCombinedQSHeaders()).thenReturn(false) splitShadeHeaderController = SplitShadeHeaderController(view, statusBarIconController, qsCarrierGroupControllerBuilder, featureFlags, batteryMeterViewController) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 2d944aa707bf..dcffd22a484e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -51,6 +51,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -137,9 +138,13 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, mKeyguardMessageAreaFactory, mShadeController); - mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, - mNotificationPanelView, mBiometrucUnlockController, - mNotificationContainer, mBypassController); + mStatusBarKeyguardViewManager.registerStatusBar( + mStatusBar, + mNotificationPanelView, + new PanelExpansionStateManager(), + mBiometrucUnlockController, + mNotificationContainer, + mBypassController); mStatusBarKeyguardViewManager.show(null); } @@ -179,8 +184,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { public void onPanelExpansionChanged_neverHidesFullscreenBouncer() { // TODO: StatusBar should not be here, mBouncer.isFullscreenBouncer() should do the same. when(mStatusBar.isFullScreenUserSwitcherState()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */, - true /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ 0.5f, + /* expanded= */ false, + /* tracking= */ true); verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE)); } @@ -188,51 +195,67 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { public void onPanelExpansionChanged_neverHidesScrimmedBouncer() { when(mBouncer.isShowing()).thenReturn(true); when(mBouncer.isScrimmed()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */, - true /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ 0.5f, + /* expanded= */ false, + /* tracking= */ true); verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE)); } @Test public void onPanelExpansionChanged_neverShowsDuringHintAnimation() { when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */, - true /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ 0.5f, + /* expanded= */ false, + /* tracking= */ true); verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN)); } @Test public void onPanelExpansionChanged_propagatesToBouncer() { - mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */, - true /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ 0.5f, + /* expanded= */ false, + /* tracking= */ true); verify(mBouncer).setExpansion(eq(0.5f)); } @Test public void onPanelExpansionChanged_showsBouncerWhenSwiping() { when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */, - true /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ 0.5f, + /* expanded= */ false, + /* tracking= */ true); verify(mBouncer).show(eq(false), eq(false)); // But not when it's already visible reset(mBouncer); when(mBouncer.isShowing()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */, true /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ 0.5f, + /* expanded= */ false, + /* tracking= */ true); verify(mBouncer, never()).show(eq(false), eq(false)); // Or animating away reset(mBouncer); when(mBouncer.isAnimatingAway()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */, true /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ 0.5f, + /* expanded= */ false, + /* tracking= */ true); verify(mBouncer, never()).show(eq(false), eq(false)); } @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() { mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animate */); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */, - true /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ 0.5f, + /* expanded= */ false, + /* tracking= */ true); verify(mBouncer, never()).setExpansion(eq(0.5f)); } @@ -240,16 +263,20 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() { when(mBiometrucUnlockController.getMode()) .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(KeyguardBouncer.EXPANSION_VISIBLE, - false /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* expanded= */ true, + /* tracking= */ false); verify(mBouncer, never()).setExpansion(anyFloat()); } @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() { when(mStatusBar.isInLaunchTransition()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged(KeyguardBouncer.EXPANSION_VISIBLE, - false /* tracking */); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* expanded= */ true, + /* tracking= */ false); verify(mBouncer, never()).setExpansion(anyFloat()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index c80c07249cc5..4e6b0a26609b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -37,6 +37,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.ForegroundServiceNotificationListener; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -51,6 +52,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -109,12 +111,15 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(DozeScrimController.class), mock(ScrimController.class), mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class), mock(KeyguardStateController.class), - mock(KeyguardIndicationController.class), mStatusBar, + mock(KeyguardIndicationController.class), + mock(FeatureFlags.class), + mStatusBar, mock(ShadeControllerImpl.class), mock(LockscreenShadeTransitionController.class), mCommandQueue, mock(NotificationViewHierarchyManager.class), mock(NotificationLockscreenUserManager.class), mock(SysuiStatusBarStateController.class), + mock(NotifShadeEventSource.class), mock(NotificationEntryManager.class), mock(NotificationMediaManager.class), mock(NotificationGutsManager.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 943d3c79984d..9202cecf63db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -78,7 +78,6 @@ import com.android.systemui.InitController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; @@ -96,7 +95,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.ScreenPinningRequest; -import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -121,6 +120,7 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; @@ -132,6 +132,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -210,6 +211,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private AssistManager mAssistManager; + @Mock private NotifShadeEventSource mNotifShadeEventSource; @Mock private NotificationEntryManager mNotificationEntryManager; @Mock private NotificationGutsManager mNotificationGutsManager; @Mock private NotificationMediaManager mNotificationMediaManager; @@ -243,7 +245,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private StatusBarComponent mStatusBarComponent; @Mock private PluginManager mPluginManager; @Mock private LegacySplitScreen mLegacySplitScreen; - @Mock private StatusBarWindowView mStatusBarWindowView; @Mock private LightsOutNotifController mLightsOutNotifController; @Mock private ViewMediatorCallback mViewMediatorCallback; @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; @@ -257,7 +258,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy; @Mock private DemoModeController mDemoModeController; @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; - @Mock private BrightnessSlider.Factory mBrightnessSliderFactory; + @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory; @Mock private UnfoldTransitionConfig mUnfoldTransitionConfig; @Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy; @Mock private Lazy<NaturalRotationUnfoldProgressProvider> mNaturalRotationProgressProvider; @@ -279,7 +280,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; @Mock private PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; - @Mock private DialogLaunchAnimator mDialogLaunchAnimator; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -377,11 +377,13 @@ public class StatusBarTest extends SysuiTestCase { new FalsingManagerFake(), new FalsingCollectorFake(), mBroadcastDispatcher, + mNotifShadeEventSource, mNotificationEntryManager, mNotificationGutsManager, notificationLogger, mNotificationInterruptStateProvider, mNotificationViewHierarchyManager, + new PanelExpansionStateManager(), mKeyguardViewMediator, new DisplayMetrics(), mMetricsLogger, @@ -421,7 +423,6 @@ public class StatusBarTest extends SysuiTestCase { mLightsOutNotifController, mStatusBarNotificationActivityStarterBuilder, mShadeController, - mStatusBarWindowView, mStatusBarKeyguardViewManager, mViewMediatorCallback, mInitController, @@ -459,11 +460,14 @@ public class StatusBarTest extends SysuiTestCase { Optional.of(mStartingSurface), mTunerService, mock(DumpManager.class), - mActivityLaunchAnimator, - mDialogLaunchAnimator); - when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), - any(NotificationPanelViewController.class), any(BiometricUnlockController.class), - any(ViewGroup.class), any(KeyguardBypassController.class))) + mActivityLaunchAnimator); + when(mKeyguardViewMediator.registerStatusBar( + any(StatusBar.class), + any(NotificationPanelViewController.class), + any(PanelExpansionStateManager.class), + any(BiometricUnlockController.class), + any(ViewGroup.class), + any(KeyguardBypassController.class))) .thenReturn(mStatusBarKeyguardViewManager); when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt new file mode 100644 index 000000000000..a8a33dabf1aa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -0,0 +1,92 @@ +/* + * 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.systemui.statusbar.phone + +import android.animation.Animator +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.statusbar.StatusBarStateControllerImpl +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.settings.GlobalSettings +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.spy +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { + + private lateinit var controller: UnlockedScreenOffAnimationController + @Mock + private lateinit var keyguardViewMediator: KeyguardViewMediator + @Mock + private lateinit var dozeParameters: DozeParameters + @Mock + private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var globalSettings: GlobalSettings + @Mock + private lateinit var statusbar: StatusBar + @Mock + private lateinit var lightRevealScrim: LightRevealScrim + @Mock + private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock + private lateinit var statusBarStateController: StatusBarStateControllerImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + controller = UnlockedScreenOffAnimationController( + context, + wakefulnessLifecycle, + statusBarStateController, + dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator }, + keyguardStateController, + dagger.Lazy<DozeParameters> { dozeParameters }, + globalSettings + ) + controller.initialize(statusbar, lightRevealScrim) + } + + @Test + fun testAnimClearsEndListener() { + val keyguardView = View(context) + val animator = spy(keyguardView.animate()) + val keyguardSpy = spy(keyguardView) + Mockito.`when`(keyguardSpy.animate()).thenReturn(animator) + val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java) + controller.animateInKeyguard(keyguardSpy, Runnable {}) + Mockito.verify(animator).setListener(listener.capture()) + // Verify that the listener is cleared when it ends + listener.value.onAnimationEnd(null) + Mockito.verify(animator).setListener(null) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt new file mode 100644 index 000000000000..e09cde917285 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt @@ -0,0 +1,80 @@ +/* + * 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.systemui.statusbar.phone.panelstate + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test + +@SmallTest +class PanelExpansionStateManagerTest : SysuiTestCase() { + + private lateinit var panelExpansionStateManager: PanelExpansionStateManager + + @Before + fun setUp() { + panelExpansionStateManager = PanelExpansionStateManager() + } + + @Test + fun onPanelExpansionChanged_listenersNotified() { + val listener = TestPanelExpansionListener() + panelExpansionStateManager.addListener(listener) + val fraction = 0.6f + val expanded = true + val tracking = true + + panelExpansionStateManager.onPanelExpansionChanged(fraction, expanded, tracking) + + assertThat(listener.fraction).isEqualTo(fraction) + assertThat(listener.expanded).isEqualTo(expanded) + assertThat(listener.tracking).isEqualTo(tracking) + } + + @Test + fun addPanelExpansionListener_listenerNotifiedOfCurrentValues() { + val fraction = 0.6f + val expanded = true + val tracking = true + panelExpansionStateManager.onPanelExpansionChanged(fraction, expanded, tracking) + val listener = TestPanelExpansionListener() + + panelExpansionStateManager.addListener(listener) + + assertThat(listener.fraction).isEqualTo(fraction) + assertThat(listener.expanded).isEqualTo(expanded) + assertThat(listener.tracking).isEqualTo(tracking) + } + + class TestPanelExpansionListener : PanelExpansionListener { + var fraction: Float = 0f + var expanded: Boolean = false + var tracking: Boolean = false + + override fun onPanelExpansionChanged( + fraction: Float, + expanded: Boolean, + tracking: Boolean + ) { + this.fraction = fraction + this.expanded = expanded + this.tracking = tracking + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index dd8354dedafd..97e1edb9ac74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -205,7 +205,7 @@ public class RemoteInputViewTest extends SysuiTestCase { ExpandableNotificationRow row = helper.createRow(); RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); - view.setOnVisibilityChangedListener(null); + view.addOnVisibilityChangedListener(null); view.setVisibility(View.INVISIBLE); view.setVisibility(View.VISIBLE); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt new file mode 100644 index 000000000000..2662da201460 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt @@ -0,0 +1,135 @@ +/* + * 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.systemui.util + +import android.test.suitebuilder.annotation.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ListenerSetTest : SysuiTestCase() { + + var runnableSet: ListenerSet<Runnable> = ListenerSet() + + @Before + fun setup() { + runnableSet = ListenerSet() + } + + @Test + fun addIfAbsent_doesNotDoubleAdd() { + // setup & preconditions + val runnable1 = Runnable { } + val runnable2 = Runnable { } + assertThat(runnableSet.toList()).isEmpty() + + // Test that an element can be added + assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() + assertThat(runnableSet.toList()).containsExactly(runnable1) + + // Test that a second element can be added + assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + + // Test that re-adding the first element does nothing and returns false + assertThat(runnableSet.addIfAbsent(runnable1)).isFalse() + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + } + + @Test + fun remove_removesListener() { + // setup and preconditions + val runnable1 = Runnable { } + val runnable2 = Runnable { } + assertThat(runnableSet.toList()).isEmpty() + runnableSet.addIfAbsent(runnable1) + runnableSet.addIfAbsent(runnable2) + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + + // Test that removing the first runnable only removes that one runnable + assertThat(runnableSet.remove(runnable1)).isTrue() + assertThat(runnableSet.toList()).containsExactly(runnable2) + + // Test that removing a non-present runnable does not error + assertThat(runnableSet.remove(runnable1)).isFalse() + assertThat(runnableSet.toList()).containsExactly(runnable2) + + // Test that removing the other runnable succeeds + assertThat(runnableSet.remove(runnable2)).isTrue() + assertThat(runnableSet.toList()).isEmpty() + } + + @Test + fun remove_isReentrantSafe() { + // Setup and preconditions + val runnablesCalled = mutableListOf<Int>() + // runnable1 is configured to remove itself + val runnable1 = object : Runnable { + override fun run() { + runnableSet.remove(this) + runnablesCalled.add(1) + } + } + val runnable2 = Runnable { + runnablesCalled.add(2) + } + assertThat(runnableSet.toList()).isEmpty() + runnableSet.addIfAbsent(runnable1) + runnableSet.addIfAbsent(runnable2) + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + + // Test that both runnables are called and 1 was removed + for (runnable in runnableSet) { + runnable.run() + } + assertThat(runnablesCalled).containsExactly(1, 2) + assertThat(runnableSet.toList()).containsExactly(runnable2) + } + + @Test + fun addIfAbsent_isReentrantSafe() { + // Setup and preconditions + val runnablesCalled = mutableListOf<Int>() + val runnable99 = Runnable { + runnablesCalled.add(99) + } + // runnable1 is configured to add runnable99 + val runnable1 = Runnable { + runnableSet.addIfAbsent(runnable99) + runnablesCalled.add(1) + } + val runnable2 = Runnable { + runnablesCalled.add(2) + } + assertThat(runnableSet.toList()).isEmpty() + runnableSet.addIfAbsent(runnable1) + runnableSet.addIfAbsent(runnable2) + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + + // Test that both original runnables are called and 99 was added but not called + for (runnable in runnableSet) { + runnable.run() + } + assertThat(runnablesCalled).containsExactly(1, 2) + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt index bff99bf340d6..483dc9fc42b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt @@ -58,3 +58,24 @@ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() */ inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> = ArgumentCaptor.forClass(T::class.java) + +/** + * Helper function for creating new mocks, without the need to pass in a [Class] instance. + * + * Generic T is nullable because implicitly bounded by Any?. + */ +inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java) + +/** + * Helper function for creating and using a single-use ArgumentCaptor in kotlin. + * + * val captor = argumentCaptor<Foo>() + * verify(...).someMethod(captor.capture()) + * val captured = captor.value + * + * becomes: + * + * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) } + */ +inline fun <reified T : Any> withArgCaptor(block: ArgumentCaptor<T>.() -> Unit): T = + argumentCaptor<T>().apply { block() }.value
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 1159e0912926..15a92dcf26b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -185,6 +185,8 @@ public class BubblesTest extends SysuiTestCase { private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor; @Captor private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor; + @Captor + private ArgumentCaptor<List<Bubble>> mBubbleListCaptor; private BubblesManager mBubblesManager; // TODO(178618782): Move tests on the controller directly to the shell @@ -1165,6 +1167,22 @@ public class BubblesTest extends SysuiTestCase { verify(mDataRepository, times(1)).loadBubbles(anyInt(), any()); } + /** + * Verifies that shortcut deletions triggers that bubble being removed from XML. + */ + @Test + public void testDeleteShortcutsDeletesXml() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + BubbleEntry shortcutBubbleEntry = BubblesManager.notifToBubbleEntry(row.getEntry()); + mBubbleController.updateBubble(shortcutBubbleEntry); + + mBubbleData.dismissBubbleWithKey(shortcutBubbleEntry.getKey(), + Bubbles.DISMISS_SHORTCUT_REMOVED); + + verify(mDataRepository, atLeastOnce()).removeBubbles(anyInt(), mBubbleListCaptor.capture()); + assertThat(mBubbleListCaptor.getValue().get(0).getKey()).isEqualTo( + shortcutBubbleEntry.getKey()); + } /** * Verifies that the package manager for the user is used when loading info for the bubble. diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 05c4822f288e..43b181ed3ab9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -93,6 +93,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; import com.android.wm.shell.bubbles.BubbleEntry; @@ -167,6 +168,9 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Captor private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor; + @Captor + private ArgumentCaptor<List<Bubble>> mBubbleListCaptor; + private BubblesManager mBubblesManager; private TestableBubbleController mBubbleController; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; @@ -1013,6 +1017,23 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { verify(mDataRepository, times(1)).loadBubbles(anyInt(), any()); } + /** + * Verifies that shortcut deletions triggers that bubble being removed from XML. + */ + @Test + public void testDeleteShortcutsDeletesXml() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + BubbleEntry shortcutBubbleEntry = BubblesManager.notifToBubbleEntry(row.getEntry()); + mBubbleController.updateBubble(shortcutBubbleEntry); + + mBubbleData.dismissBubbleWithKey(shortcutBubbleEntry.getKey(), + Bubbles.DISMISS_SHORTCUT_REMOVED); + + verify(mDataRepository, atLeastOnce()).removeBubbles(anyInt(), mBubbleListCaptor.capture()); + assertThat(mBubbleListCaptor.getValue().get(0).getKey()).isEqualTo( + shortcutBubbleEntry.getKey()); + } + @Test public void testShowManageMenuChangesSysuiState() { mBubbleController.updateBubble(mBubbleEntry); diff --git a/packages/services/CameraExtensionsProxy/AndroidManifest.xml b/packages/services/CameraExtensionsProxy/AndroidManifest.xml index ef1d581d717e..79c9d13cd925 100644 --- a/packages/services/CameraExtensionsProxy/AndroidManifest.xml +++ b/packages/services/CameraExtensionsProxy/AndroidManifest.xml @@ -2,6 +2,12 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.cameraextensions"> + <queries> + <intent> + <action android:name="androidx.camera.extensions.action.VENDOR_ACTION" /> + </intent> + </queries> + <application android:label="@string/app_name" android:defaultToDeviceProtectedStorage="true" diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 4946ad442b0a..1af8ad344190 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -187,7 +187,7 @@ public class AppPredictionPerUserService extends @NonNull IPredictionCallback callback) { final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); if (sessionInfo == null) return; - final boolean serviceExists = resolveService(sessionId, false, + final boolean serviceExists = resolveService(sessionId, true, sessionInfo.mUsesPeopleService, s -> s.registerPredictionUpdates(sessionId, callback)); if (serviceExists) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 4748a86b79c6..7c1e2da4d6a3 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -457,6 +457,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind }, FgThread.getExecutor()).whenComplete(uncheckExceptions((association, err) -> { if (err == null) { addAssociation(association, userId); + mServiceConnectors.forUser(userId).post(service -> { + service.onAssociationCreated(); + }); } else { Slog.e(LOG_TAG, "Failed to discover device(s)", err); callback.onFailure("No devices found: " + err.getMessage()); @@ -1448,8 +1451,12 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } void restartBleScan() { - mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback); - startBleScan(); + if (mBluetoothAdapter.getBluetoothLeScanner() != null) { + mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback); + startBleScan(); + } else { + Slog.w(LOG_TAG, "BluetoothLeScanner is null (likely BLE isn't ON yet)."); + } } private List<ScanFilter> getBleScanFilters() { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 8a42ddfdc19d..61d784ecb5f3 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -223,7 +223,7 @@ public final class ContentCaptureManagerService extends @Override // from AbstractMasterSystemService protected ContentCapturePerUserService newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled) { - return new ContentCapturePerUserService(this, mLock, disabled, resolvedUserId); + return new ContentCapturePerUserService(this, mLock, disabled, resolvedUserId, mHandler); } @Override // from SystemService @@ -1072,26 +1072,18 @@ public final class ContentCaptureManagerService extends ParcelFileDescriptor sourceOut = servicePipe.second; ParcelFileDescriptor sinkOut = servicePipe.first; - mParentService.mPackagesWithShareRequests.add(mDataShareRequest.getPackageName()); - - try { - mClientAdapter.write(sourceIn); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to call write() the client operation", e); - sendErrorSignal(mClientAdapter, serviceAdapter, - ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN); - logServiceEvent( - CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL); - return; + synchronized (mParentService.mLock) { + mParentService.mPackagesWithShareRequests.add(mDataShareRequest.getPackageName()); } - try { - serviceAdapter.start(sinkOut); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to call start() the service operation", e); + + if (!setUpSharingPipeline(mClientAdapter, serviceAdapter, sourceIn, sinkOut)) { sendErrorSignal(mClientAdapter, serviceAdapter, ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN); - logServiceEvent( - CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_SERVICE_PIPE_FAIL); + bestEffortCloseFileDescriptors(sourceIn, sinkIn, sourceOut, sinkOut); + synchronized (mParentService.mLock) { + mParentService.mPackagesWithShareRequests + .remove(mDataShareRequest.getPackageName()); + } return; } @@ -1184,6 +1176,32 @@ public final class ContentCaptureManagerService extends } } + private boolean setUpSharingPipeline( + IDataShareWriteAdapter clientAdapter, + IDataShareReadAdapter serviceAdapter, + ParcelFileDescriptor sourceIn, + ParcelFileDescriptor sinkOut) { + try { + clientAdapter.write(sourceIn); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to call write() the client operation", e); + logServiceEvent( + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL); + return false; + } + + try { + serviceAdapter.start(sinkOut); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to call start() the service operation", e); + logServiceEvent( + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_SERVICE_PIPE_FAIL); + return false; + } + + return true; + } + private void enforceDataSharingTtl(ParcelFileDescriptor sourceIn, ParcelFileDescriptor sinkIn, ParcelFileDescriptor sourceOut, diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 6bd1fa6c335d..822a42bf50cf 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -46,6 +46,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings; @@ -75,6 +76,7 @@ import com.android.server.contentcapture.RemoteContentCaptureService.ContentCapt import com.android.server.infra.AbstractPerUserSystemService; import java.io.PrintWriter; +import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -88,6 +90,10 @@ final class ContentCapturePerUserService private static final String TAG = ContentCapturePerUserService.class.getSimpleName(); + private static final int MAX_REBIND_COUNTS = 5; + // 5 minutes + private static final long REBIND_DURATION_MS = 5 * 60 * 1_000; + @GuardedBy("mLock") private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>(); @@ -121,11 +127,18 @@ final class ContentCapturePerUserService @GuardedBy("mLock") private ContentCaptureServiceInfo mInfo; + private Instant mLastRebindTime; + private int mRebindCount; + private final Handler mHandler; + + private final Runnable mReBindServiceRunnable = new RebindServiceRunnable(); + // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's ContentCapturePerUserService(@NonNull ContentCaptureManagerService master, - @NonNull Object lock, boolean disabled, @UserIdInt int userId) { + @NonNull Object lock, boolean disabled, @UserIdInt int userId, Handler handler) { super(master, lock, userId); + mHandler = handler; updateRemoteServiceLocked(disabled); } @@ -190,9 +203,43 @@ final class ContentCapturePerUserService Slog.w(TAG, "remote service died: " + service); synchronized (mLock) { mZombie = true; - writeServiceEvent( - FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED, - getServiceComponentName()); + // Reset rebindCount if over 12 hours mLastRebindTime + if (mLastRebindTime != null && Instant.now().isAfter( + mLastRebindTime.plusMillis(12 * 60 * 60 * 1000))) { + if (mMaster.debug) { + Slog.i(TAG, "The current rebind count " + mRebindCount + " is reset."); + } + mRebindCount = 0; + } + if (mRebindCount >= MAX_REBIND_COUNTS) { + writeServiceEvent( + FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED, + getServiceComponentName()); + } + if (mRebindCount < MAX_REBIND_COUNTS) { + mHandler.removeCallbacks(mReBindServiceRunnable); + mHandler.postDelayed(mReBindServiceRunnable, REBIND_DURATION_MS); + } + } + } + + private void updateRemoteServiceAndResurrectSessionsLocked() { + boolean disabled = !isEnabledLocked(); + updateRemoteServiceLocked(disabled); + resurrectSessionsLocked(); + } + + private final class RebindServiceRunnable implements Runnable{ + + @Override + public void run() { + synchronized (mLock) { + if (mZombie) { + mLastRebindTime = Instant.now(); + mRebindCount++; + updateRemoteServiceAndResurrectSessionsLocked(); + } + } } } @@ -240,8 +287,8 @@ final class ContentCapturePerUserService } void onPackageUpdatedLocked() { - updateRemoteServiceLocked(!isEnabledLocked()); - resurrectSessionsLocked(); + mRebindCount = 0; + updateRemoteServiceAndResurrectSessionsLocked(); } @GuardedBy("mLock") @@ -555,6 +602,8 @@ final class ContentCapturePerUserService mInfo.dump(prefix2, pw); } pw.print(prefix); pw.print("Zombie: "); pw.println(mZombie); + pw.print(prefix); pw.print("Rebind count: "); pw.println(mRebindCount); + pw.print(prefix); pw.print("Last rebind: "); pw.println(mLastRebindTime); if (mRemoteService != null) { pw.print(prefix); pw.println("remote service:"); diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 61b8ded60db7..031f6eeeca5f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -168,10 +168,6 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> return Utils.isKeyguard(getContext(), getOwnerString()); } - private boolean isSettings() { - return Utils.isSettings(getContext(), getOwnerString()); - } - @Override protected boolean isCryptoOperation() { return mOperationId != 0; @@ -503,8 +499,6 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> protected int getShowOverlayReason() { if (isKeyguard()) { return BiometricOverlayConstants.REASON_AUTH_KEYGUARD; - } else if (isSettings()) { - return BiometricOverlayConstants.REASON_AUTH_SETTINGS; } else if (isBiometricPrompt()) { return BiometricOverlayConstants.REASON_AUTH_BP; } else { diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 9d2cff9901e2..5ce72c2fd080 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -108,8 +108,9 @@ public class CameraServiceProxy extends SystemService /** * When enabled this change id forces the packages it is applied to override the default - * camera rotate & crop behavior. The default behavior along with all possible override - * combinations is discussed in the table below. + * camera rotate & crop behavior and always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE . + * The default behavior along with all possible override combinations is discussed in the table + * below. */ @ChangeId @Overridable @@ -121,9 +122,7 @@ public class CameraServiceProxy extends SystemService * When enabled this change id forces the packages it is applied to ignore the current value of * 'android:resizeableActivity' as well as target SDK equal to or below M and consider the * activity as non-resizeable. In this case, the value of camera rotate & crop will only depend - * on potential mismatches between the orientation of the camera and the fixed orientation of - * the activity. You can check the table below for further details on the possible override - * combinations. + * on the needed compensation considering the current display rotation. */ @ChangeId @Overridable @@ -132,67 +131,30 @@ public class CameraServiceProxy extends SystemService public static final long OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK = 191513214L; // buganizer id /** - * This change id forces the packages it is applied to override the default camera rotate & crop - * behavior. Enabling it will set the crop & rotate parameter to - * {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_90} and disabling it - * will reset the parameter to - * {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_NONE} as long as camera - * clients include {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_AUTO} - * in their capture requests. - * - * This treatment only takes effect if OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS is also enabled. - * The table below includes further information about the possible override combinations. - */ - @ChangeId - @Overridable - @Disabled - @TestApi - public static final long OVERRIDE_CAMERA_ROTATE_AND_CROP = 190069291L; //buganizer id - - /** * Possible override combinations * - * |OVERRIDE | |OVERRIDE_ - * |CAMERA_ |OVERRIDE |CAMERA_ - * |ROTATE_ |CAMERA_ |RESIZEABLE_ - * |AND_CROP_ |ROTATE_ |AND_SDK_ - * |DEFAULTS |AND_CROP |CHECK - * ______________________________________________ - * Default | | | - * Behavior | D |D |D - * ______________________________________________ - * Ignore | | | - * SDK&Resize | D |D |E - * ______________________________________________ - * Default | | | - * Behavior | D |E |D - * ______________________________________________ - * Ignore | | | - * SDK&Resize | D |E |E - * ______________________________________________ - * Rotate&Crop| | | - * disabled | E |D |D - * ______________________________________________ - * Rotate&Crop| | | - * disabled | E |D |E - * ______________________________________________ - * Rotate&Crop| | | - * enabled | E |E |D - * ______________________________________________ - * Rotate&Crop| | | - * enabled | E |E |E - * ______________________________________________ + * |OVERRIDE |OVERRIDE_ + * |CAMERA_ |CAMERA_ + * |ROTATE_ |RESIZEABLE_ + * |AND_CROP_ |AND_SDK_ + * |DEFAULTS |CHECK + * _________________________________________________ + * Default Behavior | D |D + * _________________________________________________ + * Ignore SDK&Resize | D |E + * _________________________________________________ + * SCALER_ROTATE_AND_CROP_NONE | E |D, E + * _________________________________________________ * Where: - * E -> Override enabled - * D -> Override disabled - * Default behavior -> Rotate&crop will be enabled only in cases - * where the fixed app orientation mismatches - * with the orientation of the camera. - * Additionally the app must either target M (or below) - * or is declared as non-resizeable. - * Ignore SDK&Resize -> Rotate&crop will be enabled only in cases - * where the fixed app orientation mismatches - * with the orientation of the camera. + * E -> Override enabled + * D -> Override disabled + * Default behavior -> Rotate&crop will be calculated depending on the required + * compensation necessary for the current display rotation. + * Additionally the app must either target M (or below) + * or is declared as non-resizeable. + * Ignore SDK&Resize -> The Rotate&crop value will depend on the required + * compensation for the current display rotation. + * SCALER_ROTATE_AND_CROP_NONE -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE */ // Flags arguments to NFC adapter to enable/disable NFC @@ -543,14 +505,8 @@ public class CameraServiceProxy extends SystemService if ((taskInfo != null) && (CompatChanges.isChangeEnabled( OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName, UserHandle.getUserHandleForUid(taskInfo.userId)))) { - if (CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_ROTATE_AND_CROP, packageName, - UserHandle.getUserHandleForUid(taskInfo.userId))) { - Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP enabled!"); + Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS enabled!"); return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; - } else { - Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP disabled!"); - return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; - } } boolean ignoreResizableAndSdkCheck = false; if ((taskInfo != null) && (CompatChanges.isChangeEnabled( diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index ddaaa1eeff4a..7d31287663d5 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -114,9 +114,10 @@ abstract public class ManagedServices { static final String ATT_VERSION = "version"; static final String ATT_DEFAULTS = "defaults"; static final String ATT_USER_SET = "user_set_services"; + static final String ATT_USER_SET_OLD = "user_set"; static final String ATT_USER_CHANGED = "user_changed"; - static final int DB_VERSION = 4; + static final String DB_VERSION = "4"; static final int APPROVAL_BY_PACKAGE = 0; static final int APPROVAL_BY_COMPONENT = 1; @@ -482,7 +483,7 @@ abstract public class ManagedServices { public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException { out.startTag(null, getConfig().xmlTag); - out.attributeInt(null, ATT_VERSION, DB_VERSION); + out.attributeInt(null, ATT_VERSION, Integer.parseInt(DB_VERSION)); writeDefaults(out); @@ -615,6 +616,7 @@ abstract public class ManagedServices { // read grants int type; String version = XmlUtils.readStringAttribute(parser, ATT_VERSION); + boolean needUpgradeUserset = false; readDefaults(parser); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { String tag = parser.getName(); @@ -633,13 +635,42 @@ abstract public class ManagedServices { final boolean isPrimary = parser.getAttributeBoolean(null, ATT_IS_PRIMARY, true); + // Load three different userSet attributes from xml + // user_changed, not null if version == 4 and is NAS setting final String isUserChanged = XmlUtils.readStringAttribute(parser, ATT_USER_CHANGED); - String userSetComponent = null; - if (isUserChanged == null) { - userSetComponent = XmlUtils.readStringAttribute(parser, ATT_USER_SET); + // user_set, not null if version <= 3 + final String isUserChanged_Old = XmlUtils.readStringAttribute(parser, + ATT_USER_SET_OLD); + // user_set_services, not null if version >= 3 and is non-NAS setting + String userSetComponent = XmlUtils.readStringAttribute(parser, ATT_USER_SET); + + // since the same xml version may have different userSet attributes, + // we need to check both xml version and userSet values to know how to set + // the userSetComponent/mIsUserChanged to the correct value + if (DB_VERSION.equals(version)) { + // version 4, NAS contains user_changed and + // NLS/others contain user_set_services + if (isUserChanged == null) { //NLS + userSetComponent = TextUtils.emptyIfNull(userSetComponent); + } else { //NAS + mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged)); + userSetComponent = Boolean.valueOf(isUserChanged) ? approved : ""; + } } else { - mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged)); + // version 3 may contain user_set (R) or user_set_services (S) + // version 2 or older contain user_set or nothing + needUpgradeUserset = true; + if (userSetComponent == null) { //contains user_set + if (isUserChanged_Old != null && Boolean.valueOf(isUserChanged_Old)) { + //user_set = true + userSetComponent = approved; + mIsUserChanged.put(resolvedUserId, true); + needUpgradeUserset = false; + } else { + userSetComponent = ""; + } + } } readExtraAttributes(tag, parser, resolvedUserId); if (allowedManagedServicePackages == null || allowedManagedServicePackages.test( @@ -659,7 +690,6 @@ abstract public class ManagedServices { || DB_VERSION_1.equals(version) || DB_VERSION_2.equals(version) || DB_VERSION_3.equals(version); - boolean needUpgradeUserset = DB_VERSION_3.equals(version); if (isOldVersion) { upgradeDefaultsXmlVersion(); } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 27b164830572..ee0b3d52eb3d 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -71,6 +71,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -81,7 +82,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; - import com.android.server.pm.UserManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -285,6 +285,12 @@ public final class OverlayManagerService extends SystemService { restoreSettings(); + // Wipe all shell overlays on boot, to recover from a potentially broken device + String shellPkgName = TextUtils.emptyIfNull( + getContext().getString(android.R.string.config_systemShell)); + mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated + && shellPkgName.equals(overlayInfo.packageName)); + initIfNeeded(); onSwitchUser(UserHandle.USER_SYSTEM); @@ -891,6 +897,16 @@ public final class OverlayManagerService extends SystemService { throw new IllegalArgumentException(request.typeToString() + " unsupported for user " + request.userId); } + + // Normal apps are blocked from accessing OMS via SELinux, so to block non-root, + // non privileged callers, a simple check against the shell UID is sufficient, since + // that's the only exception from the other categories. This is enough while OMS + // is not a public API, but this will have to be changed if it's ever exposed. + if (callingUid == Process.SHELL_UID) { + EventLog.writeEvent(0x534e4554, "202768292", -1, ""); + throw new IllegalArgumentException("Non-root shell cannot fabricate overlays"); + } + realUserId = UserHandle.USER_ALL; // Enforce that the calling process can only register and unregister fabricated diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index d7b244980cfc..7fdeb27203bd 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -135,6 +135,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private static final long MAX_ACTIVE_SESSIONS_NO_PERMISSION = 50; /** Upper bound on number of historical sessions for a UID */ private static final long MAX_HISTORICAL_SESSIONS = 1048576; + /** Destroy sessions older than this on storage free request */ + private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS; /** * Allow verification-skipping if it's a development app installed through ADB with @@ -334,22 +336,28 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements @GuardedBy("mSessions") private void reconcileStagesLocked(String volumeUuid) { - final File stagingDir = getTmpSessionDir(volumeUuid); - final ArraySet<File> unclaimedStages = newArraySet( - stagingDir.listFiles(sStageFilter)); - - // We also need to clean up orphaned staging directory for staged sessions - final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid); - unclaimedStages.addAll(newArraySet(stagedSessionStagingDir.listFiles())); - + final ArraySet<File> unclaimedStages = getStagingDirsOnVolume(volumeUuid); // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); unclaimedStages.remove(session.stageDir); } + removeStagingDirs(unclaimedStages); + } + + private ArraySet<File> getStagingDirsOnVolume(String volumeUuid) { + final File stagingDir = getTmpSessionDir(volumeUuid); + final ArraySet<File> stagingDirs = newArraySet(stagingDir.listFiles(sStageFilter)); + + // We also need to clean up orphaned staging directory for staged sessions + final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid); + stagingDirs.addAll(newArraySet(stagedSessionStagingDir.listFiles())); + return stagingDirs; + } + private void removeStagingDirs(ArraySet<File> stagingDirsToRemove) { // Clean up orphaned staging directories - for (File stage : unclaimedStages) { + for (File stage : stagingDirsToRemove) { Slog.w(TAG, "Deleting orphan stage " + stage); synchronized (mPm.mInstallLock) { mPm.removeCodePathLI(stage); @@ -363,6 +371,33 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + /** + * Called to free up some storage space from obsolete installation files + */ + public void freeStageDirs(String volumeUuid) { + final ArraySet<File> unclaimedStagingDirsOnVolume = getStagingDirsOnVolume(volumeUuid); + final long currentTimeMillis = System.currentTimeMillis(); + synchronized (mSessions) { + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + if (!unclaimedStagingDirsOnVolume.contains(session.stageDir)) { + // Only handles sessions stored on the target volume + continue; + } + final long age = currentTimeMillis - session.createdMillis; + if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) { + // Aggressively close old sessions because we are running low on storage + // Their staging dirs will be removed too + session.abandon(); + } else { + // Session is new enough, so it deserves to be kept even on low storage + unclaimedStagingDirsOnVolume.remove(session.stageDir); + } + } + } + removeStagingDirs(unclaimedStagingDirsOnVolume); + } + public static boolean isStageName(String name) { final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 542948491dc8..d0e445749698 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -673,7 +673,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final Runnable r; synchronized (mLock) { assertNotChildLocked("StagedSession#abandon"); - assertCallerIsOwnerOrRoot(); + assertCallerIsOwnerOrRootOrSystem(); if (isInTerminalState()) { // We keep the session in the database if it's in a finalized state. It will be // removed by PackageInstallerService when the last update time is old enough. @@ -3704,7 +3704,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void abandonNonStaged() { synchronized (mLock) { assertNotChildLocked("abandonNonStaged"); - assertCallerIsOwnerOrRoot(); + assertCallerIsOwnerOrRootOrSystem(); if (mRelinquished) { if (LOGD) Slog.d(TAG, "Ignoring abandon after commit relinquished control"); return; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1debaf3ea9b4..821900bf57fe 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9296,6 +9296,10 @@ public class PackageManagerService extends IPackageManager.Stub if (freeBytesRequired > 0) { smInternal.freeCache(volumeUuid, freeBytesRequired); } + + // 12. Clear temp install session files + mInstallerService.freeStageDirs(volumeUuid); + if (file.getUsableSpace() >= bytes) return; } else { try { @@ -12818,7 +12822,7 @@ public class PackageManagerService extends IPackageManager.Stub return; } } else { - if (isInstantApp(packageName, callingUserId)) { + if (isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID)) { return; } } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index e447a23cf331..245f4538a076 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -44,9 +44,11 @@ final class Vibration { enum Status { RUNNING, FINISHED, + FINISHED_UNEXPECTED, // Didn't terminate in the usual way. FORWARDED_TO_INPUT_DEVICES, CANCELLED, IGNORED_ERROR_APP_OPS, + IGNORED_ERROR_TOKEN, IGNORED, IGNORED_APP_OPS, IGNORED_BACKGROUND, diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 0f4f58b32c1b..25321c18bb3c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -47,6 +47,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.NoSuchElementException; import java.util.PriorityQueue; import java.util.Queue; @@ -98,7 +99,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } private final Object mLock = new Object(); - private final WorkSource mWorkSource = new WorkSource(); + private final WorkSource mWorkSource; private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; private final VibrationSettings mVibrationSettings; @@ -110,6 +111,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private volatile boolean mStop; private volatile boolean mForceStop; + // Variable only set and read in main thread. + private boolean mCalledVibrationCompleteCallback = false; VibrationThread(Vibration vib, VibrationSettings vibrationSettings, DeviceVibrationEffectAdapter effectAdapter, @@ -119,9 +122,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { mVibrationSettings = vibrationSettings; mDeviceEffectAdapter = effectAdapter; mCallbacks = callbacks; + mWorkSource = new WorkSource(mVibration.uid); mWakeLock = wakeLock; - mWorkSource.set(vib.uid); - mWakeLock.setWorkSource(mWorkSource); mBatteryStatsService = batteryStatsService; CombinedVibration effect = vib.getEffect(); @@ -151,17 +153,53 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @Override public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); + // Structured to guarantee the vibrators completed and released callbacks at the end of + // thread execution. Both of these callbacks are exclusively called from this thread. + try { + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); + runWithWakeLock(); + } finally { + clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED); + } + } finally { + mCallbacks.onVibratorsReleased(); + } + } + + /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */ + private void runWithWakeLock() { + mWakeLock.setWorkSource(mWorkSource); mWakeLock.acquire(); try { + runWithWakeLockAndDeathLink(); + } finally { + mWakeLock.release(); + } + } + + /** + * Runs the VibrationThread with the binder death link, handling link/unlink failures. + * Called from within runWithWakeLock. + */ + private void runWithWakeLockAndDeathLink() { + try { mVibration.token.linkToDeath(this, 0); - playVibration(); - mCallbacks.onVibratorsReleased(); } catch (RemoteException e) { Slog.e(TAG, "Error linking vibration to token death", e); + clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN); + return; + } + // Ensure that the unlink always occurs now. + try { + // This is the actual execution of the vibration. + playVibration(); } finally { - mVibration.token.unlinkToDeath(this, 0); - mWakeLock.release(); + try { + mVibration.token.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Slog.wtf(TAG, "Failed to unlink token", e); + } } } @@ -219,6 +257,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } + // Indicate that the vibration is complete. This can be called multiple times only for + // convenience of handling error conditions - an error after the client is complete won't + // affect the status. + private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) { + if (!mCalledVibrationCompleteCallback) { + mCalledVibrationCompleteCallback = true; + mCallbacks.onVibrationCompleted(mVibration.id, completedStatus); + } + } + private void playVibration() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration"); try { @@ -226,7 +274,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { final int sequentialEffectSize = sequentialEffect.getEffects().size(); mStepQueue.offer(new StartVibrateStep(sequentialEffect)); - Vibration.Status status = null; while (!mStepQueue.isEmpty()) { long waitTime; synchronized (mLock) { @@ -242,13 +289,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (waitTime <= 0) { mStepQueue.consumeNext(); } - Vibration.Status currentStatus = mStop ? Vibration.Status.CANCELLED + Vibration.Status status = mStop ? Vibration.Status.CANCELLED : mStepQueue.calculateVibrationStatus(sequentialEffectSize); - if (status == null && currentStatus != Vibration.Status.RUNNING) { + if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) { // First time vibration stopped running, start clean-up tasks and notify // callback immediately. - status = currentStatus; - mCallbacks.onVibrationCompleted(mVibration.id, status); + clientVibrationCompleteIfNotAlready(status); if (status == Vibration.Status.CANCELLED) { mStepQueue.cancel(); } @@ -256,19 +302,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (mForceStop) { // Cancel every step and stop playing them right away, even clean-up steps. mStepQueue.cancelImmediately(); + clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED); break; } } - - if (status == null) { - status = mStepQueue.calculateVibrationStatus(sequentialEffectSize); - if (status == Vibration.Status.RUNNING) { - Slog.w(TAG, "Something went wrong, step queue completed but vibration status" - + " is still RUNNING for vibration " + mVibration.id); - status = Vibration.Status.FINISHED; - } - mCallbacks.onVibrationCompleted(mVibration.id, status); - } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index c715c39c7359..d1374362505f 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -190,7 +190,7 @@ class ActivityMetricsLogger { @VisibleForTesting boolean allDrawn() { - return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.allDrawn(); + return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.mIsDrawn; } boolean hasActiveTransitionInfo() { @@ -224,8 +224,8 @@ class ActivityMetricsLogger { final boolean mProcessRunning; /** whether the process of the launching activity didn't have any active activity. */ final boolean mProcessSwitch; - /** The activities that should be drawn. */ - final ArrayList<ActivityRecord> mPendingDrawActivities = new ArrayList<>(2); + /** Whether the last launched activity has reported drawn. */ + boolean mIsDrawn; /** The latest activity to have been launched. */ @NonNull ActivityRecord mLastLaunchedActivity; @@ -318,10 +318,7 @@ class ActivityMetricsLogger { mLastLaunchedActivity.mLaunchRootTask = null; } mLastLaunchedActivity = r; - if (!r.noDisplay && !r.isReportedDrawn()) { - if (DEBUG_METRICS) Slog.i(TAG, "Add pending draw " + r); - mPendingDrawActivities.add(r); - } + mIsDrawn = r.isReportedDrawn(); } /** Returns {@code true} if the incoming activity can belong to this transition. */ @@ -332,29 +329,7 @@ class ActivityMetricsLogger { /** @return {@code true} if the activity matches a launched activity in this transition. */ boolean contains(ActivityRecord r) { - return r != null && (r == mLastLaunchedActivity || mPendingDrawActivities.contains(r)); - } - - /** Called when the activity is drawn or won't be drawn. */ - void removePendingDrawActivity(ActivityRecord r) { - if (DEBUG_METRICS) Slog.i(TAG, "Remove pending draw " + r); - mPendingDrawActivities.remove(r); - } - - boolean allDrawn() { - return mPendingDrawActivities.isEmpty(); - } - - /** Only keep the records which can be drawn. */ - void updatePendingDraw(boolean keepInitializing) { - for (int i = mPendingDrawActivities.size() - 1; i >= 0; i--) { - final ActivityRecord r = mPendingDrawActivities.get(i); - if (!r.mVisibleRequested - && !(keepInitializing && r.isState(ActivityRecord.State.INITIALIZING))) { - if (DEBUG_METRICS) Slog.i(TAG, "Discard pending draw " + r); - mPendingDrawActivities.remove(i); - } - } + return r == mLastLaunchedActivity; } /** @@ -377,7 +352,7 @@ class ActivityMetricsLogger { @Override public String toString() { return "TransitionInfo{" + Integer.toHexString(System.identityHashCode(this)) - + " a=" + mLastLaunchedActivity + " ua=" + mPendingDrawActivities + "}"; + + " a=" + mLastLaunchedActivity + " d=" + mIsDrawn + "}"; } } @@ -683,8 +658,7 @@ class ActivityMetricsLogger { // visible such as after the top task is finished. for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) { final TransitionInfo prevInfo = mTransitionInfoList.get(i); - prevInfo.updatePendingDraw(false /* keepInitializing */); - if (prevInfo.allDrawn()) { + if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.mVisibleRequested) { abort(prevInfo, "nothing will be drawn"); } } @@ -711,17 +685,16 @@ class ActivityMetricsLogger { if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn " + r); final TransitionInfo info = getActiveTransitionInfo(r); - if (info == null || info.allDrawn()) { - if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn no activity to be drawn"); + if (info == null || info.mIsDrawn) { + if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn not pending drawn " + info); return null; } // Always calculate the delay because the caller may need to know the individual drawn time. info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs); - info.removePendingDrawActivity(r); - info.updatePendingDraw(false /* keepInitializing */); + info.mIsDrawn = true; final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info); - if (info.mLoggedTransitionStarting && info.allDrawn()) { - done(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs); + if (info.mLoggedTransitionStarting) { + done(false /* abort */, info, "notifyWindowsDrawn", timestampNs); } if (r.mWmService.isRecentsAnimationTarget(r)) { r.mWmService.getRecentsAnimationController().logRecentsAnimationStartTime( @@ -770,12 +743,8 @@ class ActivityMetricsLogger { info.mCurrentTransitionDelayMs = info.calculateDelay(timestampNs); info.mReason = activityToReason.valueAt(index); info.mLoggedTransitionStarting = true; - // Do not remove activity in initializing state because the transition may be started - // by starting window. The initializing activity may be requested to visible soon. - info.updatePendingDraw(true /* keepInitializing */); - if (info.allDrawn()) { - done(false /* abort */, info, "notifyTransitionStarting - all windows drawn", - timestampNs); + if (info.mIsDrawn) { + done(false /* abort */, info, "notifyTransitionStarting drawn", timestampNs); } } } @@ -828,12 +797,9 @@ class ActivityMetricsLogger { return; } if (!r.mVisibleRequested || r.finishing) { - info.removePendingDrawActivity(r); - if (info.mLastLaunchedActivity == r) { - // Check if the tracker can be cancelled because the last launched activity may be - // no longer visible. - scheduleCheckActivityToBeDrawn(r, 0 /* delay */); - } + // Check if the tracker can be cancelled because the last launched activity may be + // no longer visible. + scheduleCheckActivityToBeDrawn(r, 0 /* delay */); } } @@ -852,17 +818,12 @@ class ActivityMetricsLogger { // If we have an active transition that's waiting on a certain activity that will be // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary. - // We have no active transitions. + // We have no active transitions. Or the notified activity whose visibility changed is + // no longer the launched activity, then we can still wait to get onWindowsDrawn. if (info == null) { return; } - // The notified activity whose visibility changed is no longer the launched activity. - // We can still wait to get onWindowsDrawn. - if (info.mLastLaunchedActivity != r) { - return; - } - // If the task of the launched activity contains any activity to be drawn, then the // window drawn event should report later to complete the transition. Otherwise all // activities in this task may be finished, invisible or drawn, so the transition event @@ -945,7 +906,6 @@ class ActivityMetricsLogger { } logAppTransitionFinished(info, isHibernating != null ? isHibernating : false); } - info.mPendingDrawActivities.clear(); mTransitionInfoList.remove(info); } @@ -1122,7 +1082,7 @@ class ActivityMetricsLogger { if (info == null) { return null; } - if (!info.allDrawn() && info.mPendingFullyDrawn == null) { + if (!info.mIsDrawn && info.mPendingFullyDrawn == null) { // There are still undrawn activities, postpone reporting fully drawn until all of its // windows are drawn. So that is closer to an usable state. info.mPendingFullyDrawn = () -> { diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 34e81498b1c3..b6552cb1b962 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -1330,7 +1330,7 @@ public class DisplayRotation { case ActivityInfo.SCREEN_ORIENTATION_USER: case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED: // Works with any rotation except upside down. - return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation); + return (preferredRotation >= 0) && (preferredRotation != Surface.ROTATION_180); } return false; diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index cddb1e7edb3b..badb1f5a0a12 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -119,8 +119,12 @@ class EnsureActivitiesVisibleHelper { if (adjacentTaskFragments != null && adjacentTaskFragments.contains( childTaskFragment)) { - // Everything behind two adjacent TaskFragments are occluded. - mBehindFullyOccludedContainer = true; + if (!childTaskFragment.isTranslucent(starting) + && !childTaskFragment.getAdjacentTaskFragment().isTranslucent( + starting)) { + // Everything behind two adjacent TaskFragments are occluded. + mBehindFullyOccludedContainer = true; + } continue; } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 8c056b2a43b3..d8adc512b65a 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -391,7 +391,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final ShortcutServiceInternal shortcutService = LocalServices.getService(ShortcutServiceInternal.class); final Intent[] shortcutIntents = shortcutService.createShortcutIntents( - callingUid, callingPackage, packageName, shortcutId, + UserHandle.getUserId(callingUid), callingPackage, packageName, shortcutId, user.getIdentifier(), callingPid, callingUid); if (shortcutIntents == null || shortcutIntents.length == 0) { throw new IllegalArgumentException("Invalid shortcut id"); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index f5e7967de410..662b3d578b30 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -148,7 +148,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { /** * A launch root task for activity launching with {@link FLAG_ACTIVITY_LAUNCH_ADJACENT} flag. */ - private Task mLaunchAdjacentFlagRootTask; + @VisibleForTesting + Task mLaunchAdjacentFlagRootTask; /** * A focusable root task that is purposely to be positioned at the top. Although the root diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index a21e4f2fee0d..e05457010df8 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.IApplicationThread; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; @@ -68,6 +69,7 @@ class TransitionController { private ITransitionPlayer mTransitionPlayer; final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter(); + private IApplicationThread mTransitionPlayerThread; final ActivityTaskManagerService mAtm; final TaskSnapshotController mTaskSnapshotController; @@ -136,7 +138,8 @@ class TransitionController { return mCollectingTransition; } - void registerTransitionPlayer(@Nullable ITransitionPlayer player) { + void registerTransitionPlayer(@Nullable ITransitionPlayer player, + @Nullable IApplicationThread appThread) { try { // Note: asBinder() can be null if player is same process (likely in a test). if (mTransitionPlayer != null) { @@ -149,6 +152,7 @@ class TransitionController { player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); } mTransitionPlayer = player; + mTransitionPlayerThread = appThread; } catch (RemoteException e) { throw new RuntimeException("Unable to set transition player"); } @@ -362,6 +366,9 @@ class TransitionController { } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record); mPlayingTransitions.remove(record); + if (mPlayingTransitions.isEmpty()) { + setAnimationRunning(false /* running */); + } record.finishTransition(); mRunningLock.doNotifyLocked(); } @@ -371,9 +378,22 @@ class TransitionController { throw new IllegalStateException("Trying to move non-collecting transition to playing"); } mCollectingTransition = null; + if (mPlayingTransitions.isEmpty()) { + setAnimationRunning(true /* running */); + } mPlayingTransitions.add(transition); } + private void setAnimationRunning(boolean running) { + if (mTransitionPlayerThread == null) return; + final WindowProcessController wpc = mAtm.getProcessController(mTransitionPlayerThread); + if (wpc == null) { + Slog.w(TAG, "Unable to find process for player thread=" + mTransitionPlayerThread); + return; + } + wpc.setRunningRemoteAnimation(running); + } + void abort(Transition transition) { if (transition != mCollectingTransition) { throw new IllegalStateException("Too late to abort."); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 51ecce0ec9ec..a68b09e2e307 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -32,10 +32,6 @@ import static android.os.UserHandle.USER_NULL; import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; -import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK; -import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; @@ -43,6 +39,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION; +import static com.android.server.wm.AppTransition.isTaskTransitOld; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; @@ -70,7 +67,6 @@ import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityThread; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -91,6 +87,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.SurfaceControl.Builder; import android.view.SurfaceSession; +import android.view.TaskTransitionSpec; import android.view.WindowManager; import android.view.WindowManager.TransitionOldType; import android.view.animation.Animation; @@ -2825,33 +2822,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceAnimationSources.addAll(sources); } - TaskDisplayArea taskDisplayArea = getTaskDisplayArea(); - boolean isSettingBackgroundColor = taskDisplayArea != null - && isTransitionWithBackgroundColor(transit); - - if (isSettingBackgroundColor) { - Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext(); - @ColorInt int backgroundColor = uiContext.getColor(R.color.overview_background); + AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder(); - taskDisplayArea.setBackgroundColor(backgroundColor); + if (isTaskTransitOld(transit)) { + animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor()); } - // Atomic counter to make sure the clearColor callback is only called one. - // It will be called twice in the case we cancel the animation without restart - // (in that case it will run as the cancel and finished callbacks). - final AtomicInteger callbackCounter = new AtomicInteger(0); - final Runnable clearBackgroundColorHandler = () -> { - if (callbackCounter.getAndIncrement() == 0) { - taskDisplayArea.clearBackgroundColor(); - } - }; - - final Runnable cleanUpCallback = isSettingBackgroundColor - ? clearBackgroundColorHandler : () -> {}; - - startAnimation(getPendingTransaction(), adapter, !isVisible(), - ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> cleanUpCallback.run(), - cleanUpCallback, thumbnailAdapter); + animationRunnerBuilder.build() + .startAnimation(getPendingTransaction(), adapter, !isVisible(), + ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter); if (adapter.getShowWallpaper()) { getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; @@ -2859,11 +2838,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - private boolean isTransitionWithBackgroundColor(@TransitionOldType int transit) { - return transit == TRANSIT_OLD_TASK_OPEN - || transit == TRANSIT_OLD_TASK_CLOSE - || transit == TRANSIT_OLD_TASK_TO_FRONT - || transit == TRANSIT_OLD_TASK_TO_BACK; + private @ColorInt int getTaskAnimationBackgroundColor() { + Context uiContext = mDisplayContent.getDisplayPolicy().getSystemUiContext(); + TaskTransitionSpec customSpec = mWmService.mTaskTransitionSpec; + @ColorInt int defaultFallbackColor = uiContext.getColor(R.color.overview_background); + + if (customSpec != null && customSpec.backgroundColor != 0) { + return customSpec.backgroundColor; + } + + return defaultFallbackColor; } final SurfaceAnimationRunner getSurfaceAnimationRunner() { @@ -3551,4 +3535,53 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< getPendingTransaction().setSecure(mSurfaceControl, !canScreenshot); return true; } + + private class AnimationRunnerBuilder { + /** + * Runs when the surface stops animating + */ + private final List<Runnable> mOnAnimationFinished = new LinkedList<>(); + /** + * Runs when the animation is cancelled but the surface is still animating + */ + private final List<Runnable> mOnAnimationCancelled = new LinkedList<>(); + + private void setTaskBackgroundColor(@ColorInt int backgroundColor) { + TaskDisplayArea taskDisplayArea = getTaskDisplayArea(); + + if (taskDisplayArea != null) { + taskDisplayArea.setBackgroundColor(backgroundColor); + + // Atomic counter to make sure the clearColor callback is only called one. + // It will be called twice in the case we cancel the animation without restart + // (in that case it will run as the cancel and finished callbacks). + final AtomicInteger callbackCounter = new AtomicInteger(0); + final Runnable clearBackgroundColorHandler = () -> { + if (callbackCounter.getAndIncrement() == 0) { + taskDisplayArea.clearBackgroundColor(); + } + }; + + // We want to make sure this is called both when the surface stops animating and + // also when an animation is cancelled (i.e. animation is replaced by another + // animation but and so the surface is still animating) + mOnAnimationFinished.add(clearBackgroundColorHandler); + mOnAnimationCancelled.add(clearBackgroundColorHandler); + } + } + + private IAnimationStarter build() { + return (Transaction t, AnimationAdapter adapter, boolean hidden, + @AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> { + startAnimation(getPendingTransaction(), adapter, !isVisible(), type, + (animType, anim) -> mOnAnimationFinished.forEach(Runnable::run), + () -> mOnAnimationCancelled.forEach(Runnable::run), snapshotAnim); + }; + } + } + + private interface IAnimationStarter { + void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, + @AnimationType int type, @Nullable AnimationAdapter snapshotAnim); + } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 6e706e9df003..43a4f977e73a 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -45,6 +45,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.IApplicationThread; import android.app.WindowConfiguration; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -566,17 +567,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: { final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); final Task task = wc != null ? wc.asTask() : null; + final boolean clearRoot = hop.getToTop(); if (task == null) { throw new IllegalArgumentException("Cannot set non-task as launch root: " + wc); } else if (!task.mCreatedByOrganizer) { throw new UnsupportedOperationException( "Cannot set non-organized task as adjacent flag root: " + wc); - } else if (task.getAdjacentTaskFragment() == null) { + } else if (task.getAdjacentTaskFragment() == null && !clearRoot) { throw new UnsupportedOperationException( "Cannot set non-adjacent task as adjacent flag root: " + wc); } - final boolean clearRoot = hop.getToTop(); task.getDisplayArea().setLaunchAdjacentFlagRootTask(clearRoot ? null : task); break; } @@ -1031,10 +1032,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub @Override public void registerTransitionPlayer(ITransitionPlayer player) { enforceTaskPermission("registerTransitionPlayer()"); + final int callerPid = Binder.getCallingPid(); + final int callerUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - mTransitionController.registerTransitionPlayer(player); + final WindowProcessController wpc = + mService.getProcessController(callerPid, callerUid); + IApplicationThread appThread = null; + if (wpc != null) { + appThread = wpc.getThread(); + } + mTransitionController.registerTransitionPlayer(player, appThread); } } finally { Binder.restoreCallingIdentity(ident); @@ -1177,6 +1186,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } + if (!ownerActivity.isResizeable()) { + final IllegalArgumentException exception = new IllegalArgumentException("Not allowed" + + " to operate with non-resizable owner Activity"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + return; + } // The ownerActivity has to belong to the same app as the root Activity of the target Task. final ActivityRecord rootActivity = ownerActivity.getTask().getRootActivity(); if (rootActivity.getUid() != ownerActivity.getUid()) { diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index 8e2c1f051279..761cea79df28 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -160,6 +160,64 @@ public final class DeviceStateProviderImplTest { } @Test + public void create_stateWithCancelStickyRequestFlag() { + String configString = "<device-state-config>\n" + + " <device-state>\n" + + " <identifier>1</identifier>\n" + + " <flags>\n" + + " <flag>FLAG_CANCEL_STICKY_REQUESTS</flag>\n" + + " </flags>\n" + + " <conditions/>\n" + + " </device-state>\n" + + " <device-state>\n" + + " <identifier>2</identifier>\n" + + " <conditions/>\n" + + " </device-state>\n" + + "</device-state-config>\n"; + DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString); + DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext, + config); + + DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class); + provider.setListener(listener); + + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); + final DeviceState[] expectedStates = new DeviceState[]{ + new DeviceState(1, "", DeviceState.FLAG_CANCEL_STICKY_REQUESTS), + new DeviceState(2, "", 0 /* flags */) }; + assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); + } + + @Test + public void create_stateWithInvalidFlag() { + String configString = "<device-state-config>\n" + + " <device-state>\n" + + " <identifier>1</identifier>\n" + + " <flags>\n" + + " <flag>INVALID_FLAG</flag>\n" + + " </flags>\n" + + " <conditions/>\n" + + " </device-state>\n" + + " <device-state>\n" + + " <identifier>2</identifier>\n" + + " <conditions/>\n" + + " </device-state>\n" + + "</device-state-config>\n"; + DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString); + DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext, + config); + + DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class); + provider.setListener(listener); + + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); + final DeviceState[] expectedStates = new DeviceState[]{ + new DeviceState(1, "", 0 /* flags */), + new DeviceState(2, "", 0 /* flags */) }; + assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); + } + + @Test public void create_lidSwitch() { String configString = "<device-state-config>\n" + " <device-state>\n" diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 987236c7c98c..c337ccd67db8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -379,6 +379,7 @@ public class ManagedServicesTest extends UiServiceTestCase { /** Test that restore correctly parses the user_set attribute. */ @Test public void testReadXml_restoresUserSet() throws Exception { + mVersionString = "4"; for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices( @@ -1513,7 +1514,8 @@ public class ManagedServicesTest extends UiServiceTestCase { for (int userId : mExpectedPrimary.get(service.mApprovalLevel).keySet()) { String pkgOrCmp = mExpectedPrimary.get(service.mApprovalLevel).get(userId); xml.append(getXmlEntry( - pkgOrCmp, userId, true, !(pkgOrCmp.startsWith("non.user.set.package")))); + pkgOrCmp, userId, true, + !(pkgOrCmp.startsWith("non.user.set.package")))); } for (int userId : mExpectedSecondary.get(service.mApprovalLevel).keySet()) { xml.append(getXmlEntry( @@ -1541,7 +1543,9 @@ public class ManagedServicesTest extends UiServiceTestCase { private TypedXmlPullParser getParserWithEntries(ManagedServices service, String... xmlEntries) throws Exception { final StringBuffer xml = new StringBuffer(); - xml.append("<" + service.getConfig().xmlTag + ">\n"); + xml.append("<" + service.getConfig().xmlTag + + (mVersionString != null ? " version=\"" + mVersionString + "\" " : "") + + ">\n"); for (String xmlEntry : xmlEntries) { xml.append(xmlEntry); } @@ -1726,12 +1730,19 @@ public class ManagedServicesTest extends UiServiceTestCase { } private String getXmlEntry(String approved, int userId, boolean isPrimary, boolean userSet) { + String userSetString = ""; + if (mVersionString.equals("4")) { + userSetString = + ManagedServices.ATT_USER_CHANGED + "=\"" + String.valueOf(userSet) + "\" "; + } else if (mVersionString.equals("3")) { + userSetString = + ManagedServices.ATT_USER_SET + "=\"" + (userSet ? approved : "") + "\" "; + } return "<" + ManagedServices.TAG_MANAGED_SERVICES + " " + ManagedServices.ATT_USER_ID + "=\"" + userId +"\" " + ManagedServices.ATT_IS_PRIMARY + "=\"" + isPrimary +"\" " + ManagedServices.ATT_APPROVED_LIST + "=\"" + approved +"\" " - + ManagedServices.ATT_USER_SET + "=\"" + (userSet ? approved : "") + "\" " - + "/>\n"; + + userSetString + "/>\n"; } class TestManagedServices extends ManagedServices { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 054a401d41af..4b93e35e673a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -16,6 +16,7 @@ package com.android.server.notification; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; @@ -124,6 +125,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { profileIds.add(12); when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); when(mNm.isNASMigrationDone(anyInt())).thenReturn(true); + when(mNm.canUseManagedServices(any(), anyInt(), any())).thenReturn(true); } @Test @@ -178,6 +180,92 @@ public class NotificationAssistantsTest extends UiServiceTestCase { } @Test + public void testReadXml_upgradeUserSet_preS_VersionThree() throws Exception { + String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">" + + "<service_listing approved=\"\" user=\"0\" primary=\"true\"" + + "user_set=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + verify(mAssistants, times(0)).upgradeUserSet(); + assertTrue(isUserSetServicesEmpty(mAssistants, 0)); + assertTrue(mAssistants.mIsUserChanged.get(0)); + } + + @Test + public void testReadXml_upgradeUserSet_preS_VersionOne() throws Exception { + String xml = "<enabled_assistants version=\"1\" defaults=\"b/b\">" + + "<service_listing approved=\"\" user=\"0\" primary=\"true\"" + + "user_set=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + verify(mAssistants, times(0)).upgradeUserSet(); + assertTrue(isUserSetServicesEmpty(mAssistants, 0)); + assertTrue(mAssistants.mIsUserChanged.get(0)); + } + + @Test + public void testReadXml_upgradeUserSet_preS_noUserSet() throws Exception { + String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">" + + "<service_listing approved=\"b/b\" user=\"0\" primary=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + verify(mAssistants, times(1)).upgradeUserSet(); + assertTrue(isUserSetServicesEmpty(mAssistants, 0)); + assertFalse(mAssistants.mIsUserChanged.get(0)); + } + + @Test + public void testReadXml_upgradeUserSet_preS_noUserSet_diffDefault() throws Exception { + String xml = "<enabled_assistants version=\"3\" defaults=\"a/a\">" + + "<service_listing approved=\"b/b\" user=\"0\" primary=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + verify(mAssistants, times(1)).upgradeUserSet(); + assertTrue(isUserSetServicesEmpty(mAssistants, 0)); + assertFalse(mAssistants.mIsUserChanged.get(0)); + assertEquals(new ArraySet<>(Arrays.asList(new ComponentName("a", "a"))), + mAssistants.getDefaultComponents()); + assertEquals(Arrays.asList(new ComponentName("b", "b")), + mAssistants.getAllowedComponents(0)); + } + + @Test public void testReadXml_multiApproved() throws Exception { String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">" + "<service_listing approved=\"a/a:b/b\" user=\"0\" primary=\"true\"" @@ -210,7 +298,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { verify(mNm, never()).setDefaultAssistantForUser(anyInt()); verify(mAssistants, times(1)).addApprovedList( - new ComponentName("b", "b").flattenToString(), 10, true, null); + new ComponentName("b", "b").flattenToString(), 10, true, ""); } @Test @@ -380,4 +468,11 @@ public class NotificationAssistantsTest extends UiServiceTestCase { verify(mNm, times(1)).setDefaultAssistantForUser(eq(mZero.id)); assertEquals(new ArraySet<>(), mAssistants.getDefaultComponents()); } + + // Helper function to hold mApproved lock, avoid GuardedBy lint errors + private boolean isUserSetServicesEmpty(NotificationAssistants assistant, int userId) { + synchronized (assistant.mApproved) { + return assistant.mUserSetServices.get(userId).isEmpty(); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 0b918025bb50..d4d8b86850c6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -477,7 +477,6 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { @Test public void testConsecutiveLaunch() { - mTrampolineActivity.setState(ActivityRecord.State.INITIALIZING, "test"); onActivityLaunched(mTrampolineActivity); mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent, mTrampolineActivity /* caller */, mTrampolineActivity.getUid()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index a1e8ca4aa84b..32cca47b991c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -37,6 +37,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -44,6 +45,7 @@ import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; import android.content.Intent; +import android.content.pm.ShortcutServiceInternal; import android.graphics.PixelFormat; import android.os.Binder; import android.os.IBinder; @@ -71,6 +73,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.util.ArrayList; @@ -89,6 +92,7 @@ import java.util.concurrent.TimeUnit; public class DragDropControllerTests extends WindowTestsBase { private static final int TIMEOUT_MS = 3000; private static final int TEST_UID = 12345; + private static final int TEST_PROFILE_UID = 12345 * UserHandle.PER_USER_RANGE; private static final int TEST_PID = 67890; private static final String TEST_PACKAGE = "com.test.package"; @@ -387,6 +391,32 @@ public class DragDropControllerTests extends WindowTestsBase { } } + @Test + public void testValidateProfileAppShortcutArguments_notCallingUid() { + doReturn(PERMISSION_GRANTED).when(mWm.mContext) + .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS)); + final Session session = Mockito.spy(new Session(mWm, new IWindowSessionCallback.Stub() { + @Override + public void onAnimatorScaleChanged(float scale) {} + })); + final ShortcutServiceInternal shortcutService = mock(ShortcutServiceInternal.class); + final Intent[] shortcutIntents = new Intent[1]; + shortcutIntents[0] = new Intent(); + doReturn(shortcutIntents).when(shortcutService).createShortcutIntents(anyInt(), any(), + any(), any(), anyInt(), anyInt(), anyInt()); + LocalServices.removeServiceForTest(ShortcutServiceInternal.class); + LocalServices.addService(ShortcutServiceInternal.class, shortcutService); + + ArgumentCaptor<Integer> callingUser = ArgumentCaptor.forClass(Integer.class); + session.validateAndResolveDragMimeTypeExtras( + createClipDataForShortcut("test_package", "test_shortcut_id", + mock(UserHandle.class)), + TEST_PROFILE_UID, TEST_PID, TEST_PACKAGE); + verify(shortcutService).createShortcutIntents(callingUser.capture(), any(), + any(), any(), anyInt(), anyInt(), anyInt()); + assertTrue(callingUser.getValue() == UserHandle.getUserId(TEST_PROFILE_UID)); + } + private ClipData createClipDataForShortcut(String packageName, String shortcutId, UserHandle user) { final Intent data = new Intent(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 4e77fa73fd09..d9a166a62673 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -473,7 +473,7 @@ public class TransitionTests extends WindowTestsBase { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); final TransitionController controller = new TransitionController(mAtm, snapshotController); final ITransitionPlayer player = new ITransitionPlayer.Default(); - controller.registerTransitionPlayer(player); + controller.registerTransitionPlayer(player, null /* appThread */); ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); @@ -539,7 +539,7 @@ public class TransitionTests extends WindowTestsBase { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); final TransitionController controller = new TransitionController(mAtm, snapshotController); final ITransitionPlayer player = new ITransitionPlayer.Default(); - controller.registerTransitionPlayer(player); + controller.registerTransitionPlayer(player, null /* appThread */); ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 3f0c13c83816..f366f57bae08 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -316,7 +316,8 @@ public class WallpaperControllerTests extends WindowTestsBase { final IBinder mockBinder = mock(IBinder.class); final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class); doReturn(mockBinder).when(mockPlayer).asBinder(); - mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer); + mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer, + null /* appThread */); Transition transit = mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index a482bdacfc82..1eed79f189a8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -543,6 +543,36 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + public void testSetAdjacentLaunchRoot() { + DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); + + final Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + dc, WINDOWING_MODE_MULTI_WINDOW, null); + final RunningTaskInfo info1 = task1.getTaskInfo(); + final Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + dc, WINDOWING_MODE_MULTI_WINDOW, null); + final RunningTaskInfo info2 = task2.getTaskInfo(); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setAdjacentRoots(info1.token, info2.token); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertEquals(task1.getAdjacentTaskFragment(), task2); + assertEquals(task2.getAdjacentTaskFragment(), task1); + + wct = new WindowContainerTransaction(); + wct.setLaunchAdjacentFlagRoot(info1.token); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1); + + task1.setAdjacentTaskFragment(null); + task2.setAdjacentTaskFragment(null); + wct = new WindowContainerTransaction(); + wct.clearLaunchAdjacentFlagRoot(info1.token); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null); + } + + @Test public void testTileAddRemoveChild() { final StubOrganizer listener = new StubOrganizer(); mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); 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 81b00eae0021..115f8a3fecf4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -802,7 +802,7 @@ class WindowTestsBase extends SystemServiceTestsBase { TestTransitionPlayer registerTestTransitionPlayer() { final TestTransitionPlayer testPlayer = new TestTransitionPlayer( mAtm.getTransitionController(), mAtm.mWindowOrganizerController); - testPlayer.mController.registerTransitionPlayer(testPlayer); + testPlayer.mController.registerTransitionPlayer(testPlayer, null /* appThread */); return testPlayer; } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index cfc145a49d4a..54d3af520d3c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -963,6 +963,21 @@ public class CarrierConfigManager { = "carrier_use_ims_first_for_emergency_bool"; /** + * When {@code true}, the determination of whether to place a call as an emergency call will be + * based on the known {@link android.telephony.emergency.EmergencyNumber}s for the SIM on which + * the call is being placed. In a dual SIM scenario, if Sim A has the emergency numbers + * 123, 456 and Sim B has the emergency numbers 789, and the user places a call on SIM A to 789, + * it will not be treated as an emergency call in this case. + * When {@code false}, the determination is based on the emergency numbers from all device SIMs, + * regardless of which SIM the call is being placed on. If Sim A has the emergency numbers + * 123, 456 and Sim B has the emergency numbers 789, and the user places a call on SIM A to 789, + * the call will be dialed as an emergency number, but with an unspecified routing. + * @hide + */ + public static final String KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL = + "use_only_dialed_sim_ecc_list_bool"; + + /** * When IMS instant lettering is available for a carrier (see * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines the list of characters * which may not be contained in messages. Should be specified as a regular expression suitable @@ -5265,6 +5280,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true); + sDefaults.putBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false); sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, ""); sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, ""); sDefaults.putString(KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING, ""); diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index e890acb36b48..9572154ec773 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -36,6 +36,7 @@ import com.android.telephony.Rlog; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -152,16 +153,9 @@ public final class TelephonyScanManager { throw new RuntimeException( "Failed to find NetworkScanInfo with id " + message.arg2); } - NetworkScanCallback callback = nsi.mCallback; - Executor executor = nsi.mExecutor; - if (callback == null) { - throw new RuntimeException( - "Failed to find NetworkScanCallback with id " + message.arg2); - } - if (executor == null) { - throw new RuntimeException( - "Failed to find Executor with id " + message.arg2); - } + + final NetworkScanCallback callback = nsi.mCallback; + final Executor executor = nsi.mExecutor; switch (message.what) { case CALLBACK_RESTRICTED_SCAN_RESULTS: @@ -246,17 +240,24 @@ public final class TelephonyScanManager { NetworkScanRequest request, Executor executor, NetworkScanCallback callback, String callingPackage, @Nullable String callingFeatureId) { try { + Objects.requireNonNull(request, "Request was null"); + Objects.requireNonNull(callback, "Callback was null"); + Objects.requireNonNull(executor, "Executor was null"); final ITelephony telephony = getITelephony(); if (telephony == null) return null; - int scanId = telephony.requestNetworkScan( - subId, request, mMessenger, new Binder(), callingPackage, - callingFeatureId); - if (scanId == INVALID_SCAN_ID) { - Rlog.e(TAG, "Failed to initiate network scan"); - return null; - } + // The lock must be taken before calling requestNetworkScan because the resulting + // scanId can be invoked asynchronously on another thread at any time after + // requestNetworkScan invoked, leaving a critical section between that call and adding + // the record to the ScanInfo cache. synchronized (mScanInfo) { + int scanId = telephony.requestNetworkScan( + subId, request, mMessenger, new Binder(), callingPackage, + callingFeatureId); + if (scanId == INVALID_SCAN_ID) { + Rlog.e(TAG, "Failed to initiate network scan"); + return null; + } // We link to death whenever a scan is started to ensure that we are linked // at the point that phone process death might matter. // We never unlink because: |