diff options
142 files changed, 3830 insertions, 1252 deletions
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index b6189692107e..ec100c2fa0c0 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -115,6 +115,7 @@ import android.location.CountryDetector; import android.location.ICountryDetector; import android.location.ILocationManager; import android.location.LocationManager; +import android.media.AudioDeviceVolumeManager; import android.media.AudioManager; import android.media.MediaFrameworkInitializer; import android.media.MediaFrameworkPlatformInitializer; @@ -339,6 +340,13 @@ public final class SystemServiceRegistry { return new AudioManager(ctx); }}); + registerService(Context.AUDIO_DEVICE_VOLUME_SERVICE, AudioDeviceVolumeManager.class, + new CachedServiceFetcher<AudioDeviceVolumeManager>() { + @Override + public AudioDeviceVolumeManager createService(ContextImpl ctx) { + return new AudioDeviceVolumeManager(ctx); + }}); + registerService(Context.MEDIA_ROUTER_SERVICE, MediaRouter.class, new CachedServiceFetcher<MediaRouter>() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index fce23cf6819a..77ca48a8ed1d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3846,6 +3846,7 @@ public abstract class Context { WIFI_RTT_RANGING_SERVICE, NSD_SERVICE, AUDIO_SERVICE, + AUDIO_DEVICE_VOLUME_SERVICE, AUTH_SERVICE, FINGERPRINT_SERVICE, //@hide: FACE_SERVICE, @@ -4687,6 +4688,17 @@ public abstract class Context { public static final String AUDIO_SERVICE = "audio"; /** + * @hide + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.media.AudioDeviceVolumeManager} for handling management of audio device + * (e.g. speaker, USB headset) volume. + * + * @see #getSystemService(String) + * @see android.media.AudioDeviceVolumeManager + */ + public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume"; + + /** * Use with {@link #getSystemService(String)} to retrieve a {@link * android.media.MediaTranscodingManager} for transcoding media. * diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 0956a71bd92d..666f316f7c1d 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -414,6 +414,53 @@ public final class TransitionInfo implements Parcelable { return false; } + /** + * Releases temporary-for-animation surfaces referenced by this to potentially free up memory. + * This includes root-leash and snapshots. + */ + public void releaseAnimSurfaces() { + for (int i = mChanges.size() - 1; i >= 0; --i) { + final Change c = mChanges.get(i); + if (c.mSnapshot != null) { + c.mSnapshot.release(); + c.mSnapshot = null; + } + } + if (mRootLeash != null) { + mRootLeash.release(); + } + } + + /** + * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this + * if the surface-controls get stored and used elsewhere in the process. To just release + * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}. + */ + public void releaseAllSurfaces() { + releaseAnimSurfaces(); + for (int i = mChanges.size() - 1; i >= 0; --i) { + mChanges.get(i).getLeash().release(); + } + } + + /** + * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol + * refcounts are incremented which allows the "remote" receiver to release them without breaking + * the caller's references. Use this only if you need to "send" this to a local function which + * assumes it is being called from a remote caller. + */ + public TransitionInfo localRemoteCopy() { + final TransitionInfo out = new TransitionInfo(mType, mFlags); + for (int i = 0; i < mChanges.size(); ++i) { + out.mChanges.add(mChanges.get(i).localRemoteCopy()); + } + out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null; + // Doesn't have any native stuff, so no need for actual copy + out.mOptions = mOptions; + out.mRootOffset.set(mRootOffset); + return out; + } + /** Represents the change a WindowContainer undergoes during a transition */ public static final class Change implements Parcelable { private final WindowContainerToken mContainer; @@ -466,6 +513,27 @@ public final class TransitionInfo implements Parcelable { mSnapshotLuma = in.readFloat(); } + private Change localRemoteCopy() { + final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote")); + out.mParent = mParent; + out.mLastParent = mLastParent; + out.mMode = mMode; + out.mFlags = mFlags; + out.mStartAbsBounds.set(mStartAbsBounds); + out.mEndAbsBounds.set(mEndAbsBounds); + out.mEndRelOffset.set(mEndRelOffset); + out.mTaskInfo = mTaskInfo; + out.mAllowEnterPip = mAllowEnterPip; + out.mStartRotation = mStartRotation; + out.mEndRotation = mEndRotation; + out.mEndFixedRotation = mEndFixedRotation; + out.mRotationAnimation = mRotationAnimation; + out.mBackgroundColor = mBackgroundColor; + out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null; + out.mSnapshotLuma = mSnapshotLuma; + return out; + } + /** Sets the parent of this change's container. The parent must be a participant or null. */ public void setParent(@Nullable WindowContainerToken parent) { mParent = parent; diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index fdc3e5af8d8b..f2ae973500af 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -146,7 +146,7 @@ public abstract class WindowProviderService extends Service implements WindowPro @SuppressLint("OnNameExpected") @Override - public void onConfigurationChanged(@Nullable Configuration configuration) { + public void onConfigurationChanged(@NonNull Configuration configuration) { // This is only called from WindowTokenClient. mCallbacksController.dispatchConfigurationChanged(configuration); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index f170e774739f..8ba2583757cd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -63,6 +63,7 @@ import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import android.view.Choreographer; import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; @@ -179,8 +180,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // This is necessary in case there was a resize animation ongoing when exit PIP // started, in which case the first resize will be skipped to let the exit // operation handle the final resize out of PIP mode. See b/185306679. - finishResize(tx, destinationBounds, direction, animationType); - sendOnPipTransitionFinished(direction); + finishResizeDelayedIfNeeded(() -> { + finishResize(tx, destinationBounds, direction, animationType); + sendOnPipTransitionFinished(direction); + }); } } @@ -196,6 +199,39 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }; + /** + * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu. + * + * This is done to avoid a race condition between the last transaction applied in + * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in + * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a + * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally, + * the WCT should be the last transaction to finish the animation. However, it may happen that + * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This + * happens only when the PiP surface transaction has to be synced with the PiP menu due to the + * necessity for a delay when syncing the PiP surface animation with the PiP menu surface + * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after + * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds. + * + * To avoid this, we delay the finishResize operation until + * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application. + */ + private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) { + if (!shouldSyncPipTransactionWithMenu()) { + finishResizeRunnable.run(); + return; + } + + // Delay the finishResize to the next frame + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { + mMainExecutor.execute(finishResizeRunnable); + }, null); + } + + private boolean shouldSyncPipTransactionWithMenu() { + return mPipMenuController.isMenuVisible(); + } + @VisibleForTesting final PipTransitionController.PipTransitionCallback mPipTransitionCallback = new PipTransitionController.PipTransitionCallback() { @@ -221,7 +257,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @Override public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds) { - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.movePipMenu(leash, tx, destinationBounds); return true; } @@ -1223,7 +1259,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .crop(tx, mLeash, toBounds) .round(tx, mLeash, mPipTransitionState.isInPip()); - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.resizePipMenu(mLeash, tx, toBounds); } else { tx.apply(); @@ -1265,7 +1301,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .scale(tx, mLeash, startBounds, toBounds, degrees) .round(tx, mLeash, startBounds, toBounds); - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.movePipMenu(mLeash, tx, toBounds); } else { tx.apply(); 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 4e1fa290270d..485b400f458d 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 @@ -77,10 +77,10 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { if (mRemote.asBinder() != null) { mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); } + if (sct != null) { + finishTransaction.merge(sct); + } mMainExecutor.execute(() -> { - if (sct != null) { - finishTransaction.merge(sct); - } finishCallback.onTransitionFinished(wct, null /* wctCB */); }); } @@ -90,7 +90,13 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { if (mRemote.asBinder() != null) { mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */); } - mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb); + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal( + startTransaction, mRemote.getRemoteTransition()); + final TransitionInfo remoteInfo = + remoteStartT == startTransaction ? info : info.localRemoteCopy(); + mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb); // assume that remote will apply the start transaction. startTransaction.clear(); } catch (RemoteException e) { @@ -124,7 +130,13 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { } }; try { - mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb); + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteT = + RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition()); + final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy(); + mRemote.getRemoteTransition().mergeAnimation( + transition, remoteInfo, remoteT, mergeTarget, cb); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error merging 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 9469529de8f1..b4e05848882c 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 @@ -19,6 +19,7 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; @@ -120,10 +121,10 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { public void onTransitionFinished(WindowContainerTransaction wct, SurfaceControl.Transaction sct) { unhandleDeath(remote.asBinder(), finishCallback); + if (sct != null) { + finishTransaction.merge(sct); + } mMainExecutor.execute(() -> { - if (sct != null) { - finishTransaction.merge(sct); - } mRequestedRemotes.remove(transition); finishCallback.onTransitionFinished(wct, null /* wctCB */); }); @@ -131,8 +132,14 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { }; Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread()); try { + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteStartT = + copyIfLocal(startTransaction, remote.getRemoteTransition()); + final TransitionInfo remoteInfo = + remoteStartT == startTransaction ? info : info.localRemoteCopy(); handleDeath(remote.asBinder(), finishCallback); - remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb); + remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb); // assume that remote will apply the start transaction. startTransaction.clear(); } catch (RemoteException e) { @@ -145,6 +152,28 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { return true; } + static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t, + IRemoteTransition remote) { + // We care more about parceling than local (though they should be the same); so, use + // queryLocalInterface since that's what Binder uses to decide if it needs to parcel. + if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) { + // No local interface, so binder itself will parcel and thus we don't need to. + return t; + } + // Binder won't be parceling; however, the remotes assume they have their own native + // objects (and don't know if caller is local or not), so we need to make a COPY here so + // that the remote can clean it up without clearing the original transaction. + // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead. + final Parcel p = Parcel.obtain(); + try { + t.writeToParcel(p, 0); + p.setDataPosition(0); + return SurfaceControl.Transaction.CREATOR.createFromParcel(p); + } finally { + p.recycle(); + } + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -175,7 +204,11 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } }; try { - remote.mergeAnimation(transition, info, t, mergeTarget, cb); + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote); + final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy(); + remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e); } 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 857decf65567..b714d2e5e1e0 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 @@ -500,6 +500,7 @@ public class Transitions implements RemoteCallable<Transitions> { // Treat this as an abort since we are bypassing any merge logic and effectively // finishing immediately. onAbort(transitionToken); + releaseSurfaces(info); return; } @@ -604,6 +605,15 @@ public class Transitions implements RemoteCallable<Transitions> { onFinish(transition, wct, wctCB, false /* abort */); } + /** + * Releases an info's animation-surfaces. These don't need to persist and we need to release + * them asap so that SF can free memory sooner. + */ + private void releaseSurfaces(@Nullable TransitionInfo info) { + if (info == null) return; + info.releaseAnimSurfaces(); + } + private void onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB, @@ -642,6 +652,11 @@ public class Transitions implements RemoteCallable<Transitions> { } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished (abort=%b), notifying core %s", abort, transition); + if (active.mStartT != null) { + // Applied by now, so close immediately. Do not set to null yet, though, since nullness + // is used later to disambiguate malformed transitions. + active.mStartT.close(); + } // Merge all relevant transactions together SurfaceControl.Transaction fullFinish = active.mFinishT; for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) { @@ -661,12 +676,14 @@ public class Transitions implements RemoteCallable<Transitions> { fullFinish.apply(); } // Now perform all the finishes. + releaseSurfaces(active.mInfo); mActiveTransitions.remove(activeIdx); mOrganizer.finishTransition(transition, wct, wctCB); while (activeIdx < mActiveTransitions.size()) { if (!mActiveTransitions.get(activeIdx).mMerged) break; ActiveTransition merged = mActiveTransitions.remove(activeIdx); mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */); + releaseSurfaces(merged.mInfo); } // sift through aborted transitions while (mActiveTransitions.size() > activeIdx @@ -679,8 +696,9 @@ public class Transitions implements RemoteCallable<Transitions> { } mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */); for (int i = 0; i < mObservers.size(); ++i) { - mObservers.get(i).onTransitionFinished(active.mToken, true); + mObservers.get(i).onTransitionFinished(aborted.mToken, true); } + releaseSurfaces(aborted.mInfo); } if (mActiveTransitions.size() <= activeIdx) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations " diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java index c70887672f9e..4aee9eb0488e 100644 --- a/media/java/android/media/AudioDeviceVolumeManager.java +++ b/media/java/android/media/AudioDeviceVolumeManager.java @@ -41,8 +41,7 @@ import java.util.concurrent.Executor; */ public class AudioDeviceVolumeManager { - // define when using Log.* - //private static final String TAG = "AudioDeviceVolumeManager"; + private static final String TAG = "AudioDeviceVolumeManager"; /** Indicates no special treatment in the handling of the volume adjustment */ public static final int ADJUST_MODE_NORMAL = 0; @@ -62,11 +61,15 @@ public class AudioDeviceVolumeManager { private static IAudioService sService; private final @NonNull String mPackageName; - private final @Nullable String mAttributionTag; - public AudioDeviceVolumeManager(Context context) { + /** + * @hide + * Constructor + * @param context the Context for the device volume operations + */ + public AudioDeviceVolumeManager(@NonNull Context context) { + Objects.requireNonNull(context); mPackageName = context.getApplicationContext().getOpPackageName(); - mAttributionTag = context.getApplicationContext().getAttributionTag(); } /** @@ -308,13 +311,36 @@ public class AudioDeviceVolumeManager { @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) { try { - getService().setDeviceVolume(vi, ada, mPackageName, mAttributionTag); + getService().setDeviceVolume(vi, ada, mPackageName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns the volume on the given audio device for the given volume information. + * For instance if using a {@link VolumeInfo} configured for {@link AudioManager#STREAM_ALARM}, + * it will return the alarm volume. When no volume index has ever been set for the given + * device, the default volume will be returned (the volume setting that would have been + * applied if playback for that use case had started). + * @param vi the volume information, only stream-based volumes are supported. Information + * other than the stream type is ignored. + * @param ada the device for which volume is to be retrieved + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi, + @NonNull AudioDeviceAttributes ada) { + try { + return getService().getDeviceVolume(vi, ada, mPackageName); } catch (RemoteException e) { e.rethrowFromSystemServer(); } + return VolumeInfo.getDefaultVolumeInfo(); } /** + * @hide * Return human-readable name for volume behavior * @param behavior one of the volume behaviors defined in AudioManager * @return a string for the given behavior diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e7eda3ea4552..798688ea7b46 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1212,7 +1212,13 @@ public class AudioManager { } } - private static boolean isPublicStreamType(int streamType) { + /** + * @hide + * Checks whether a stream type is part of the public SDK + * @param streamType + * @return true if the stream type is available in SDK + */ + public static boolean isPublicStreamType(int streamType) { switch (streamType) { case STREAM_VOICE_CALL: case STREAM_SYSTEM: diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 90eb9e644f26..ad933e02c0d5 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -99,7 +99,10 @@ interface IAudioService { in String callingPackage, in String attributionTag); void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada, - in String callingPackage, in String attributionTag); + in String callingPackage); + + VolumeInfo getDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada, + in String callingPackage); oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv, String callingPackage, String caller); diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index 8a03afb77942..d6fe68253be6 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -86,8 +86,10 @@ public abstract class Image implements AutoCloseable { * * <p> * The format is one of the values from - * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the - * formats and the planes is as follows: + * {@link android.graphics.ImageFormat ImageFormat}, + * {@link android.graphics.PixelFormat PixelFormat}, or + * {@link android.hardware.HardwareBuffer HardwareBuffer}. The mapping between the + * formats and the planes is as follows (any formats not listed will have 1 plane): * </p> * * <table> @@ -171,15 +173,18 @@ public abstract class Image implements AutoCloseable { * </tr> * <tr> * <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td> - * <td>1</td> + * <td>3</td> * <td>P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane - * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit - * little-endian value, with the lower 6 bits set to zero. + * followed by a Wx(H/2) Cb and Cr planes. Each sample is represented by a 16-bit + * little-endian value, with the lower 6 bits set to zero. Since this is guaranteed to be + * a semi-planar format, the Cb plane can also be treated as an interleaved Cb/Cr plane. * </td> * </tr> * </table> * * @see android.graphics.ImageFormat + * @see android.graphics.PixelFormat + * @see android.hardware.HardwareBuffer */ public abstract int getFormat(); diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 82c3139bb4d3..b0917c756a21 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -89,6 +89,7 @@ public class Ringtone { .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); + private boolean mPreferBuiltinDevice; // playback properties, use synchronized with mPlaybackSettingsLock private boolean mIsLooping = false; private float mVolume = 1.0f; @@ -157,7 +158,39 @@ public class Ringtone { } /** + * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is + * the one on which outgoing audio for SIM calls is played. + * + * @param audioManager the audio manage. + * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if + * none can be found. + */ + private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) { + AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device : deviceList) { + if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { + return device; + } + } + return null; + } + + /** + * Sets the preferred device of the ringtong playback to the built-in device. + * + * @hide + */ + public boolean preferBuiltinDevice(boolean enable) { + mPreferBuiltinDevice = enable; + if (mLocalPlayer == null) { + return true; + } + return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager)); + } + + /** * Creates a local media player for the ringtone using currently set attributes. + * * @hide */ public void createLocalMediaPlayer() { @@ -172,6 +205,8 @@ public class Ringtone { try { mLocalPlayer.setDataSource(mContext, mUri); mLocalPlayer.setAudioAttributes(mAudioAttributes); + mLocalPlayer.setPreferredDevice( + mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null); synchronized (mPlaybackSettingsLock) { applyPlaybackProperties_sync(); } diff --git a/media/java/android/media/VolumeInfo.java b/media/java/android/media/VolumeInfo.java index c61b0e57db1a..bc91a09cf681 100644 --- a/media/java/android/media/VolumeInfo.java +++ b/media/java/android/media/VolumeInfo.java @@ -27,7 +27,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; -import java.util.List; import java.util.Objects; /** @@ -35,8 +34,9 @@ import java.util.Objects; * A class to represent type of volume information. * Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}. * Volume index is optional when used to represent a category of volume. - * Index ranges are supported too, making the representation of volume changes agnostic to the - * range (e.g. can be used to map BT A2DP absolute volume range to internal range). + * Volume ranges are supported too, making the representation of volume changes agnostic + * regarding the range of values that are supported (e.g. can be used to map BT A2DP absolute + * volume range to internal range). * * Note: this class is not yet part of the SystemApi but is intended to be gradually introduced * particularly in parts of the audio framework that suffer from code ambiguity when @@ -46,25 +46,27 @@ public final class VolumeInfo implements Parcelable { private static final String TAG = "VolumeInfo"; private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used + private final boolean mHasMuteCommand; private final boolean mIsMuted; private final int mVolIndex; private final int mMinVolIndex; private final int mMaxVolIndex; - private final int mVolGroupId; - private final int mStreamType; + private final @Nullable AudioVolumeGroup mVolGroup; + private final @AudioManager.PublicStreamTypes int mStreamType; private static IAudioService sService; private static VolumeInfo sDefaultVolumeInfo; - private VolumeInfo(boolean usesStreamType, boolean isMuted, int volIndex, - int minVolIndex, int maxVolIndex, - int volGroupId, int streamType) { + private VolumeInfo(boolean usesStreamType, boolean hasMuteCommand, boolean isMuted, + int volIndex, int minVolIndex, int maxVolIndex, + AudioVolumeGroup volGroup, int streamType) { mUsesStreamType = usesStreamType; + mHasMuteCommand = hasMuteCommand; mIsMuted = isMuted; mVolIndex = volIndex; mMinVolIndex = minVolIndex; mMaxVolIndex = maxVolIndex; - mVolGroupId = volGroupId; + mVolGroup = volGroup; mStreamType = streamType; } @@ -81,8 +83,10 @@ public final class VolumeInfo implements Parcelable { /** * Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false. * @return a stream type value, see AudioManager.STREAM_* + * @throws IllegalStateException when called on a VolumeInfo not configured for + * stream types. */ - public int getStreamType() { + public @AudioManager.PublicStreamTypes int getStreamType() { if (!mUsesStreamType) { throw new IllegalStateException("VolumeInfo doesn't use stream types"); } @@ -101,24 +105,28 @@ public final class VolumeInfo implements Parcelable { /** * Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned * false. - * @return the volume group corresponding to this VolumeInfo, or null if an error occurred - * in the volume group management + * @return the volume group corresponding to this VolumeInfo + * @throws IllegalStateException when called on a VolumeInfo not configured for + * volume groups. */ - public @Nullable AudioVolumeGroup getVolumeGroup() { + public @NonNull AudioVolumeGroup getVolumeGroup() { if (mUsesStreamType) { throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup"); } - List<AudioVolumeGroup> volGroups = AudioVolumeGroup.getAudioVolumeGroups(); - for (AudioVolumeGroup group : volGroups) { - if (group.getId() == mVolGroupId) { - return group; - } - } - return null; + return mVolGroup; + } + + /** + * Return whether this instance is conveying a mute state + * @return true if the muted state was explicitly set for this instance + */ + public boolean hasMuteCommand() { + return mHasMuteCommand; } /** - * Returns whether this instance is conveying a mute state. + * Returns whether this instance is conveying a mute state that was explicitly set + * by {@link Builder#setMuted(boolean)}, false otherwise * @return true if the volume state is muted */ public boolean isMuted() { @@ -185,18 +193,21 @@ public final class VolumeInfo implements Parcelable { */ public static final class Builder { private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used - private int mStreamType = AudioManager.STREAM_MUSIC; + private @AudioManager.PublicStreamTypes int mStreamType = AudioManager.STREAM_MUSIC; + private boolean mHasMuteCommand = false; private boolean mIsMuted = false; private int mVolIndex = INDEX_NOT_SET; private int mMinVolIndex = INDEX_NOT_SET; private int mMaxVolIndex = INDEX_NOT_SET; - private int mVolGroupId = -Integer.MIN_VALUE; + private @Nullable AudioVolumeGroup mVolGroup; /** * Builder constructor for stream type-based VolumeInfo */ - public Builder(int streamType) { - // TODO validate stream type + public Builder(@AudioManager.PublicStreamTypes int streamType) { + if (!AudioManager.isPublicStreamType(streamType)) { + throw new IllegalArgumentException("Not a valid public stream type " + streamType); + } mUsesStreamType = true; mStreamType = streamType; } @@ -208,7 +219,7 @@ public final class VolumeInfo implements Parcelable { Objects.requireNonNull(volGroup); mUsesStreamType = false; mStreamType = -Integer.MIN_VALUE; - mVolGroupId = volGroup.getId(); + mVolGroup = volGroup; } /** @@ -219,11 +230,12 @@ public final class VolumeInfo implements Parcelable { Objects.requireNonNull(info); mUsesStreamType = info.mUsesStreamType; mStreamType = info.mStreamType; + mHasMuteCommand = info.mHasMuteCommand; mIsMuted = info.mIsMuted; mVolIndex = info.mVolIndex; mMinVolIndex = info.mMinVolIndex; mMaxVolIndex = info.mMaxVolIndex; - mVolGroupId = info.mVolGroupId; + mVolGroup = info.mVolGroup; } /** @@ -232,6 +244,7 @@ public final class VolumeInfo implements Parcelable { * @return the same builder instance */ public @NonNull Builder setMuted(boolean isMuted) { + mHasMuteCommand = true; mIsMuted = isMuted; return this; } @@ -241,7 +254,6 @@ public final class VolumeInfo implements Parcelable { * @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown * @return the same builder instance */ - // TODO should we allow muted true + volume index set? (useful when toggling mute on/off?) public @NonNull Builder setVolumeIndex(int volIndex) { if (volIndex != INDEX_NOT_SET && volIndex < 0) { throw new IllegalArgumentException("Volume index cannot be negative"); @@ -296,9 +308,9 @@ public final class VolumeInfo implements Parcelable { throw new IllegalArgumentException("Min volume index:" + mMinVolIndex + " greater than max index:" + mMaxVolIndex); } - return new VolumeInfo(mUsesStreamType, mIsMuted, + return new VolumeInfo(mUsesStreamType, mHasMuteCommand, mIsMuted, mVolIndex, mMinVolIndex, mMaxVolIndex, - mVolGroupId, mStreamType); + mVolGroup, mStreamType); } } @@ -306,8 +318,8 @@ public final class VolumeInfo implements Parcelable { // Parcelable @Override public int hashCode() { - return Objects.hash(mUsesStreamType, mStreamType, mIsMuted, - mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroupId); + return Objects.hash(mUsesStreamType, mHasMuteCommand, mStreamType, mIsMuted, + mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroup); } @Override @@ -318,19 +330,20 @@ public final class VolumeInfo implements Parcelable { VolumeInfo that = (VolumeInfo) o; return ((mUsesStreamType == that.mUsesStreamType) && (mStreamType == that.mStreamType) - && (mIsMuted == that.mIsMuted) - && (mVolIndex == that.mVolIndex) - && (mMinVolIndex == that.mMinVolIndex) - && (mMaxVolIndex == that.mMaxVolIndex) - && (mVolGroupId == that.mVolGroupId)); + && (mHasMuteCommand == that.mHasMuteCommand) + && (mIsMuted == that.mIsMuted) + && (mVolIndex == that.mVolIndex) + && (mMinVolIndex == that.mMinVolIndex) + && (mMaxVolIndex == that.mMaxVolIndex) + && Objects.equals(mVolGroup, that.mVolGroup)); } @Override public String toString() { return new String("VolumeInfo:" + (mUsesStreamType ? (" streamType:" + mStreamType) - : (" volGroupId" + mVolGroupId)) - + " muted:" + mIsMuted + : (" volGroup:" + mVolGroup)) + + (mHasMuteCommand ? (" muted:" + mIsMuted) : ("[no mute cmd]")) + ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "") + ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "") + ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : "")); @@ -345,21 +358,29 @@ public final class VolumeInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeBoolean(mUsesStreamType); dest.writeInt(mStreamType); + dest.writeBoolean(mHasMuteCommand); dest.writeBoolean(mIsMuted); dest.writeInt(mVolIndex); dest.writeInt(mMinVolIndex); dest.writeInt(mMaxVolIndex); - dest.writeInt(mVolGroupId); + if (!mUsesStreamType) { + mVolGroup.writeToParcel(dest, 0 /*ignored*/); + } } private VolumeInfo(@NonNull Parcel in) { mUsesStreamType = in.readBoolean(); mStreamType = in.readInt(); + mHasMuteCommand = in.readBoolean(); mIsMuted = in.readBoolean(); mVolIndex = in.readInt(); mMinVolIndex = in.readInt(); mMaxVolIndex = in.readInt(); - mVolGroupId = in.readInt(); + if (!mUsesStreamType) { + mVolGroup = AudioVolumeGroup.CREATOR.createFromParcel(in); + } else { + mVolGroup = null; + } } public static final @NonNull Parcelable.Creator<VolumeInfo> CREATOR = diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 3c6f18c76f83..e624441ca777 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -167,25 +167,14 @@ java_library { } android_library { - name: "SystemUI-tests", + name: "SystemUI-tests-base", manifest: "tests/AndroidManifest-base.xml", - additional_manifests: ["tests/AndroidManifest.xml"], - resource_dirs: [ "tests/res", "res-product", "res-keyguard", "res", ], - srcs: [ - "tests/src/**/*.kt", - "tests/src/**/*.java", - "src/**/*.kt", - "src/**/*.java", - "src/**/I*.aidl", - ":ReleaseJavaFiles", - ":SystemUI-tests-utils", - ], static_libs: [ "WifiTrackerLib", "SystemUIAnimationLib", @@ -224,9 +213,6 @@ android_library { "metrics-helper-lib", "hamcrest-library", "androidx.test.rules", - "androidx.test.uiautomator", - "mockito-target-extended-minus-junit4", - "androidx.test.ext.junit", "testables", "truth-prebuilt", "monet", @@ -236,6 +222,27 @@ android_library { "LowLightDreamLib", "motion_tool_lib", ], +} + +android_library { + name: "SystemUI-tests", + manifest: "tests/AndroidManifest-base.xml", + additional_manifests: ["tests/AndroidManifest.xml"], + srcs: [ + "tests/src/**/*.kt", + "tests/src/**/*.java", + "src/**/*.kt", + "src/**/*.java", + "src/**/I*.aidl", + ":ReleaseJavaFiles", + ":SystemUI-tests-utils", + ], + static_libs: [ + "SystemUI-tests-base", + "androidx.test.uiautomator", + "mockito-target-extended-minus-junit4", + "androidx.test.ext.junit", + ], libs: [ "android.test.runner", "android.test.base", @@ -249,6 +256,45 @@ android_library { plugins: ["dagger2-compiler"], } +android_app { + name: "SystemUIRobo-stub", + defaults: [ + "platform_app_defaults", + "SystemUI_app_defaults", + ], + manifest: "tests/AndroidManifest-base.xml", + static_libs: [ + "SystemUI-tests-base", + ], + aaptflags: [ + "--extra-packages", + "com.android.systemui", + ], + dont_merge_manifests: true, + platform_apis: true, + system_ext_specific: true, + certificate: "platform", + privileged: true, + resource_dirs: [], +} + +android_robolectric_test { + name: "SystemUiRoboTests", + srcs: [ + "tests/robolectric/src/**/*.kt", + "tests/robolectric/src/**/*.java", + ], + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + "truth-prebuilt", + ], + kotlincflags: ["-Xjvm-default=enable"], + instrumentation_for: "SystemUIRobo-stub", + java_resource_dirs: ["tests/robolectric/config"], +} + // Opt-out config for optimizing the SystemUI target using R8. // Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via // `SYSTEMUI_OPTIMIZE_JAVA := false`. diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt index f9c6841f96b5..43bfa74119b3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt @@ -320,9 +320,7 @@ class RemoteTransitionAdapter { counterWallpaper.cleanUp(finishTransaction) // Release surface references now. This is apparently to free GPU // memory while doing quick operations (eg. during CTS). - for (i in info.changes.indices.reversed()) { - info.changes[i].leash.release() - } + info.releaseAllSurfaces() for (i in leashMap.size - 1 downTo 0) { leashMap.valueAt(i).release() } @@ -331,6 +329,7 @@ class RemoteTransitionAdapter { null /* wct */, finishTransaction ) + finishTransaction.close() } catch (e: RemoteException) { Log.e( "ActivityOptionsCompat", @@ -364,6 +363,9 @@ class RemoteTransitionAdapter { ) { // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, // ignore any incoming merges. + // Clean up stuff though cuz GC takes too long for benchmark tests. + t.close() + info.releaseAllSurfaces() } } } diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt index e611e8bf0068..979e1a08b7d4 100644 --- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt +++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt @@ -38,12 +38,18 @@ import platform.test.screenshot.ScreenshotTestRule import platform.test.screenshot.getEmulatedDevicePathConfig /** A rule for Compose screenshot diff tests. */ -class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { +class ComposeScreenshotTestRule( + emulationSpec: DeviceEmulationSpec, + assetPathRelativeToBuildRoot: String +) : TestRule { private val colorsRule = MaterialYouColorsRule() private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + SystemUIGoldenImagePathManager( + getEmulatedDevicePathConfig(emulationSpec), + assetPathRelativeToBuildRoot + ) ) private val composeRule = createAndroidComposeRule<ScreenshotActivity>() private val delegateRule = diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt new file mode 100644 index 000000000000..f490c5459d46 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.shared.quickaffordance.data.content + +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine + +class FakeKeyguardQuickAffordanceProviderClient( + slots: List<KeyguardQuickAffordanceProviderClient.Slot> = + listOf( + KeyguardQuickAffordanceProviderClient.Slot( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + capacity = 1, + ), + KeyguardQuickAffordanceProviderClient.Slot( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + capacity = 1, + ), + ), + affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> = + listOf( + KeyguardQuickAffordanceProviderClient.Affordance( + id = AFFORDANCE_1, + name = AFFORDANCE_1, + iconResourceId = 0, + ), + KeyguardQuickAffordanceProviderClient.Affordance( + id = AFFORDANCE_2, + name = AFFORDANCE_2, + iconResourceId = 0, + ), + KeyguardQuickAffordanceProviderClient.Affordance( + id = AFFORDANCE_3, + name = AFFORDANCE_3, + iconResourceId = 0, + ), + ), + flags: List<KeyguardQuickAffordanceProviderClient.Flag> = + listOf( + KeyguardQuickAffordanceProviderClient.Flag( + name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED, + value = true, + ) + ), +) : KeyguardQuickAffordanceProviderClient { + + private val slots = MutableStateFlow(slots) + private val affordances = MutableStateFlow(affordances) + private val flags = MutableStateFlow(flags) + + private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap()) + + override suspend fun insertSelection(slotId: String, affordanceId: String) { + val slotCapacity = + querySlots().find { it.id == slotId }?.capacity + ?: error("Slot with ID \"$slotId\" not found!") + val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList() + while (affordances.size + 1 > slotCapacity) { + affordances.removeAt(0) + } + affordances.remove(affordanceId) + affordances.add(affordanceId) + selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances } + } + + override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> { + return slots.value + } + + override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> { + return flags.value + } + + override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> { + return slots.asStateFlow() + } + + override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> { + return flags.asStateFlow() + } + + override suspend fun queryAffordances(): + List<KeyguardQuickAffordanceProviderClient.Affordance> { + return affordances.value + } + + override fun observeAffordances(): + Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> { + return affordances.asStateFlow() + } + + override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> { + return toSelectionList(selections.value, affordances.value) + } + + override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> { + return combine(selections, affordances) { selections, affordances -> + toSelectionList(selections, affordances) + } + } + + override suspend fun deleteSelection(slotId: String, affordanceId: String) { + val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList() + affordances.remove(affordanceId) + + selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances } + } + + override suspend fun deleteAllSelections(slotId: String) { + selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() } + } + + override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable { + return BitmapDrawable() + } + + fun setFlag( + name: String, + value: Boolean, + ) { + flags.value = + flags.value.toMutableList().apply { + removeIf { it.name == name } + add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value)) + } + } + + fun setSlotCapacity(slotId: String, capacity: Int) { + slots.value = + slots.value.toMutableList().apply { + val index = indexOfFirst { it.id == slotId } + check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" } + set( + index, + KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity) + ) + } + } + + fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int { + affordances.value = affordances.value + listOf(affordance) + return affordances.value.size - 1 + } + + private fun toSelectionList( + selections: Map<String, List<String>>, + affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>, + ): List<KeyguardQuickAffordanceProviderClient.Selection> { + return selections + .map { (slotId, affordanceIds) -> + affordanceIds.map { affordanceId -> + val affordanceName = + affordances.find { it.id == affordanceId }?.name + ?: error("No affordance with ID of \"$affordanceId\"!") + KeyguardQuickAffordanceProviderClient.Selection( + slotId = slotId, + affordanceId = affordanceId, + affordanceName = affordanceName, + ) + } + } + .flatten() + } + + companion object { + const val AFFORDANCE_1 = "affordance_1" + const val AFFORDANCE_2 = "affordance_2" + const val AFFORDANCE_3 = "affordance_3" + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt new file mode 100644 index 000000000000..3213b2e97ac9 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.shared.quickaffordance.data.content + +import android.annotation.SuppressLint +import android.content.ContentValues +import android.content.Context +import android.database.ContentObserver +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.net.Uri +import androidx.annotation.DrawableRes +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +/** Client for using a content provider implementing the [Contract]. */ +interface KeyguardQuickAffordanceProviderClient { + + /** + * Selects an affordance with the given ID for a slot on the lock screen with the given ID. + * + * Note that the maximum number of selected affordances on this slot is automatically enforced. + * Selecting a slot that is already full (e.g. already has a number of selected affordances at + * its maximum capacity) will automatically remove the oldest selected affordance before adding + * the one passed in this call. Additionally, selecting an affordance that's already one of the + * selected affordances on the slot will move the selected affordance to the newest location in + * the slot. + */ + suspend fun insertSelection( + slotId: String, + affordanceId: String, + ) + + /** Returns all available slots supported by the device. */ + suspend fun querySlots(): List<Slot> + + /** Returns the list of flags. */ + suspend fun queryFlags(): List<Flag> + + /** + * Returns [Flow] for observing the collection of slots. + * + * @see [querySlots] + */ + fun observeSlots(): Flow<List<Slot>> + + /** + * Returns [Flow] for observing the collection of flags. + * + * @see [queryFlags] + */ + fun observeFlags(): Flow<List<Flag>> + + /** + * Returns all available affordances supported by the device, regardless of current slot + * placement. + */ + suspend fun queryAffordances(): List<Affordance> + + /** + * Returns [Flow] for observing the collection of affordances. + * + * @see [queryAffordances] + */ + fun observeAffordances(): Flow<List<Affordance>> + + /** Returns the current slot-affordance selections. */ + suspend fun querySelections(): List<Selection> + + /** + * Returns [Flow] for observing the collection of selections. + * + * @see [querySelections] + */ + fun observeSelections(): Flow<List<Selection>> + + /** Unselects an affordance with the given ID from the slot with the given ID. */ + suspend fun deleteSelection( + slotId: String, + affordanceId: String, + ) + + /** Unselects all affordances from the slot with the given ID. */ + suspend fun deleteAllSelections( + slotId: String, + ) + + /** Returns a [Drawable] with the given ID, loaded from the system UI package. */ + suspend fun getAffordanceIcon( + @DrawableRes iconResourceId: Int, + tintColor: Int = Color.WHITE, + ): Drawable + + /** Models a slot. A position that quick affordances can be positioned in. */ + data class Slot( + /** Unique ID of the slot. */ + val id: String, + /** + * The maximum number of quick affordances that are allowed to be positioned in this slot. + */ + val capacity: Int, + ) + + /** + * Models a quick affordance. An action that can be selected by the user to appear in one or + * more slots on the lock screen. + */ + data class Affordance( + /** Unique ID of the quick affordance. */ + val id: String, + /** User-facing label for this affordance. */ + val name: String, + /** + * Resource ID for the user-facing icon for this affordance. This resource is hosted by the + * System UI process so it must be used with + * `PackageManager.getResourcesForApplication(String)`. + */ + val iconResourceId: Int, + /** + * Whether the affordance is enabled. Disabled affordances should be shown on the picker but + * should be rendered as "disabled". When tapped, the enablement properties should be used + * to populate UI that would explain to the user what to do in order to re-enable this + * affordance. + */ + val isEnabled: Boolean = true, + /** + * If the affordance is disabled, this is a set of instruction messages to be shown to the + * user when the disabled affordance is selected. The instructions should help the user + * figure out what to do in order to re-neable this affordance. + */ + val enablementInstructions: List<String>? = null, + /** + * If the affordance is disabled, this is a label for a button shown together with the set + * of instruction messages when the disabled affordance is selected. The button should help + * send the user to a flow that would help them achieve the instructions and re-enable this + * affordance. + * + * If `null`, the button should not be shown. + */ + val enablementActionText: String? = null, + /** + * If the affordance is disabled, this is a "component name" of the format + * `packageName/action` to be used as an `Intent` for `startActivity` when the action button + * (shown together with the set of instruction messages when the disabled affordance is + * selected) is clicked by the user. The button should help send the user to a flow that + * would help them achieve the instructions and re-enable this affordance. + * + * If `null`, the button should not be shown. + */ + val enablementActionComponentName: String? = null, + ) + + /** Models a selection of a quick affordance on a slot. */ + data class Selection( + /** The unique ID of the slot. */ + val slotId: String, + /** The unique ID of the quick affordance. */ + val affordanceId: String, + /** The user-visible label for the quick affordance. */ + val affordanceName: String, + ) + + /** Models a System UI flag. */ + data class Flag( + /** The name of the flag. */ + val name: String, + /** The value of the flag. */ + val value: Boolean, + ) +} + +class KeyguardQuickAffordanceProviderClientImpl( + private val context: Context, + private val backgroundDispatcher: CoroutineDispatcher, +) : KeyguardQuickAffordanceProviderClient { + + override suspend fun insertSelection( + slotId: String, + affordanceId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.insert( + Contract.SelectionTable.URI, + ContentValues().apply { + put(Contract.SelectionTable.Columns.SLOT_ID, slotId) + put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId) + } + ) + } + } + + override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.SlotTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID) + val capacityColumnIndex = + cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY) + if (idColumnIndex == -1 || capacityColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + KeyguardQuickAffordanceProviderClient.Slot( + id = cursor.getString(idColumnIndex), + capacity = cursor.getInt(capacityColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.FlagsTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val nameColumnIndex = + cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME) + val valueColumnIndex = + cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE) + if (nameColumnIndex == -1 || valueColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + KeyguardQuickAffordanceProviderClient.Flag( + name = cursor.getString(nameColumnIndex), + value = cursor.getInt(valueColumnIndex) == 1, + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> { + return observeUri(Contract.SlotTable.URI).map { querySlots() } + } + + override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> { + return observeUri(Contract.FlagsTable.URI).map { queryFlags() } + } + + override suspend fun queryAffordances(): + List<KeyguardQuickAffordanceProviderClient.Affordance> { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.AffordanceTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val idColumnIndex = + cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID) + val nameColumnIndex = + cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME) + val iconColumnIndex = + cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON) + val isEnabledColumnIndex = + cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED) + val enablementInstructionsColumnIndex = + cursor.getColumnIndex( + Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS + ) + val enablementActionTextColumnIndex = + cursor.getColumnIndex( + Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT + ) + val enablementComponentNameColumnIndex = + cursor.getColumnIndex( + Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME + ) + if ( + idColumnIndex == -1 || + nameColumnIndex == -1 || + iconColumnIndex == -1 || + isEnabledColumnIndex == -1 || + enablementInstructionsColumnIndex == -1 || + enablementActionTextColumnIndex == -1 || + enablementComponentNameColumnIndex == -1 + ) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + KeyguardQuickAffordanceProviderClient.Affordance( + id = cursor.getString(idColumnIndex), + name = cursor.getString(nameColumnIndex), + iconResourceId = cursor.getInt(iconColumnIndex), + isEnabled = cursor.getInt(isEnabledColumnIndex) == 1, + enablementInstructions = + cursor + .getString(enablementInstructionsColumnIndex) + ?.split( + Contract.AffordanceTable + .ENABLEMENT_INSTRUCTIONS_DELIMITER + ), + enablementActionText = + cursor.getString(enablementActionTextColumnIndex), + enablementActionComponentName = + cursor.getString(enablementComponentNameColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeAffordances(): + Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> { + return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() } + } + + override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.SelectionTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val slotIdColumnIndex = + cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID) + val affordanceIdColumnIndex = + cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID) + val affordanceNameColumnIndex = + cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME) + if ( + slotIdColumnIndex == -1 || + affordanceIdColumnIndex == -1 || + affordanceNameColumnIndex == -1 + ) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + KeyguardQuickAffordanceProviderClient.Selection( + slotId = cursor.getString(slotIdColumnIndex), + affordanceId = cursor.getString(affordanceIdColumnIndex), + affordanceName = cursor.getString(affordanceNameColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> { + return observeUri(Contract.SelectionTable.URI).map { querySelections() } + } + + override suspend fun deleteSelection( + slotId: String, + affordanceId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.delete( + Contract.SelectionTable.URI, + "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" + + " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?", + arrayOf( + slotId, + affordanceId, + ), + ) + } + } + + override suspend fun deleteAllSelections( + slotId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.delete( + Contract.SelectionTable.URI, + Contract.SelectionTable.Columns.SLOT_ID, + arrayOf( + slotId, + ), + ) + } + } + + @SuppressLint("UseCompatLoadingForDrawables") + override suspend fun getAffordanceIcon( + @DrawableRes iconResourceId: Int, + tintColor: Int, + ): Drawable { + return withContext(backgroundDispatcher) { + context.packageManager + .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) + .getDrawable(iconResourceId, context.theme) + .apply { setTint(tintColor) } + } + } + + private fun observeUri( + uri: Uri, + ): Flow<Unit> { + return callbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + context.contentResolver.registerContentObserver( + uri, + /* notifyForDescendants= */ true, + observer, + ) + + awaitClose { context.contentResolver.unregisterContentObserver(observer) } + } + .onStart { emit(Unit) } + } + + companion object { + private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt index 98d8d3eb9a4a..17be74b08690 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt @@ -15,7 +15,7 @@ * */ -package com.android.systemui.shared.keyguard.data.content +package com.android.systemui.shared.quickaffordance.data.content import android.content.ContentResolver import android.net.Uri diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt index 2dc7a280e423..2dc7a280e423 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml new file mode 100644 index 000000000000..e850d6823e97 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="#1f1f1f" + android:pathData="M8,22V11L6,8V2H18V8L16,11V22ZM12,15.5Q11.375,15.5 10.938,15.062Q10.5,14.625 10.5,14Q10.5,13.375 10.938,12.938Q11.375,12.5 12,12.5Q12.625,12.5 13.062,12.938Q13.5,13.375 13.5,14Q13.5,14.625 13.062,15.062Q12.625,15.5 12,15.5ZM8,5H16V4H8ZM16,7H8V7.4L10,10.4V20H14V10.4L16,7.4ZM12,12Z"/> +</vector> diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml new file mode 100644 index 000000000000..91b9ae56b145 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="#1f1f1f" + android:pathData="M6,5V2H18V5ZM12,15.5Q12.625,15.5 13.062,15.062Q13.5,14.625 13.5,14Q13.5,13.375 13.062,12.938Q12.625,12.5 12,12.5Q11.375,12.5 10.938,12.938Q10.5,13.375 10.5,14Q10.5,14.625 10.938,15.062Q11.375,15.5 12,15.5ZM8,22V11L6,8V7H18V8L16,11V22Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml b/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml index a3ed3d1f6b79..f8169d377f12 100644 --- a/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml +++ b/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml @@ -26,10 +26,10 @@ android:fillType="evenOdd"/> <path android:pathData="M27,0C12.088,0 0,12.088 0,27C0,41.912 12.088,54 27,54C41.912,54 54,41.912 54,27C54,12.088 41.912,0 27,0ZM27,3.962C39.703,3.962 50.037,14.297 50.037,27C50.037,39.703 39.703,50.038 27,50.038C14.297,50.038 3.963,39.703 3.963,27C3.963,14.297 14.297,3.962 27,3.962Z" - android:fillColor="?attr/biometricsEnrollProgress" + android:fillColor="@color/udfps_enroll_progress" android:fillType="evenOdd"/> <path android:pathData="M23.0899,38.8534L10.4199,26.1824L13.2479,23.3544L23.0899,33.1974L41.2389,15.0474L44.0679,17.8754L23.0899,38.8534Z" - android:fillColor="?attr/biometricsEnrollProgress" + android:fillColor="@color/udfps_enroll_progress" android:fillType="evenOdd"/> </vector> diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt index 49cc48321d77..e032bb9b7e30 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt @@ -34,13 +34,19 @@ import platform.test.screenshot.* /** * A rule that allows to run a screenshot diff test on a view that is hosted in another activity. */ -class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { +class ExternalViewScreenshotTestRule( + emulationSpec: DeviceEmulationSpec, + assetPathRelativeToBuildRoot: String +) : TestRule { private val colorsRule = MaterialYouColorsRule() private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + SystemUIGoldenImagePathManager( + getEmulatedDevicePathConfig(emulationSpec), + assetPathRelativeToBuildRoot + ) ) private val delegateRule = RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule) diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt index fafc7744f439..72d8c5a09852 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt @@ -23,11 +23,11 @@ import platform.test.screenshot.PathConfig /** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */ class SystemUIGoldenImagePathManager( pathConfig: PathConfig, - override val assetsPathRelativeToRepo: String = "tests/screenshot/assets" + assetsPathRelativeToBuildRoot: String ) : GoldenImagePathManager( appContext = InstrumentationRegistry.getInstrumentation().context, - assetsPathRelativeToRepo = assetsPathRelativeToRepo, + assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, deviceLocalPath = InstrumentationRegistry.getInstrumentation() .targetContext diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt index 0b0595f4405f..738b37c9eea4 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt @@ -41,13 +41,17 @@ import platform.test.screenshot.matchers.BitmapMatcher /** A rule for View screenshot diff unit tests. */ class ViewScreenshotTestRule( emulationSpec: DeviceEmulationSpec, - private val matcher: BitmapMatcher = UnitTestBitmapMatcher + private val matcher: BitmapMatcher = UnitTestBitmapMatcher, + assetsPathRelativeToBuildRoot: String ) : TestRule { private val colorsRule = MaterialYouColorsRule() private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + SystemUIGoldenImagePathManager( + getEmulatedDevicePathConfig(emulationSpec), + assetsPathRelativeToBuildRoot + ) ) private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java) private val delegateRule = diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java index 023ef319352e..6bc8faa29adf 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java @@ -117,7 +117,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, @Override public void onSampleCollected(float medianLuma) { if (mSamplingEnabled) { - updateMediaLuma(medianLuma); + updateMedianLuma(medianLuma); } } }; @@ -260,7 +260,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, } } - private void updateMediaLuma(float medianLuma) { + private void updateMedianLuma(float medianLuma) { mCurrentMedianLuma = medianLuma; // If the difference between the new luma and the current luma is larger than threshold diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java index 93c807352521..1b0dacc327c1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -166,15 +166,14 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner counterLauncher.cleanUp(finishTransaction); counterWallpaper.cleanUp(finishTransaction); // Release surface references now. This is apparently to free GPU memory - // while doing quick operations (eg. during CTS). - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - info.getChanges().get(i).getLeash().release(); - } + // before GC would. + info.releaseAllSurfaces(); // Don't release here since launcher might still be using them. Instead // let launcher release them (eg. via RemoteAnimationTargets) leashMap.clear(); try { finishCallback.onTransitionFinished(null /* wct */, finishTransaction); + finishTransaction.close(); } catch (RemoteException e) { Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + " finished callback", e); @@ -203,10 +202,13 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner synchronized (mFinishRunnables) { finishRunnable = mFinishRunnables.remove(mergeTarget); } + // Since we're not actually animating, release native memory now + t.close(); + info.releaseAllSurfaces(); if (finishRunnable == null) return; onAnimationCancelled(false /* isKeyguardOccluded */); finishRunnable.run(); } }; } -}
\ No newline at end of file +} 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 d4d3d2579b10..b7e2494ab839 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 @@ -126,15 +126,18 @@ public class RemoteTransitionCompat { public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishedCallback) { - if (!mergeTarget.equals(mToken)) return; - if (!mRecentsSession.merge(info, t, recents)) return; - try { - finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); - } catch (RemoteException e) { - Log.e(TAG, "Error merging transition.", e); + if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) { + try { + finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); + } catch (RemoteException e) { + Log.e(TAG, "Error merging transition.", e); + } + // commit taskAppeared after merge transition finished. + mRecentsSession.commitTasksAppearedIfNeeded(recents); + } else { + t.close(); + info.releaseAllSurfaces(); } - // commit taskAppeared after merge transition finished. - mRecentsSession.commitTasksAppearedIfNeeded(recents); } }; return new RemoteTransition(remote, appThread); @@ -248,6 +251,8 @@ public class RemoteTransitionCompat { } // In this case, we are "returning" to an already running app, so just consume // the merge and do nothing. + info.releaseAllSurfaces(); + t.close(); return true; } final int layer = mInfo.getChanges().size() * 3; @@ -264,6 +269,8 @@ public class RemoteTransitionCompat { t.setLayer(targets[i].leash, layer); } t.apply(); + // not using the incoming anim-only surfaces + info.releaseAnimSurfaces(); mAppearedTargets = targets; return true; } @@ -380,9 +387,7 @@ public class RemoteTransitionCompat { } // Only release the non-local created surface references. The animator is responsible // for releasing the leashes created by local. - for (int i = 0; i < mInfo.getChanges().size(); ++i) { - mInfo.getChanges().get(i).getLeash().release(); - } + mInfo.releaseAllSurfaces(); // Reset all members. mWrapped = null; mFinishCB = null; diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java index c5190e828e35..ea808eb19b90 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java @@ -135,7 +135,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { mPowerManager.userActivity(SystemClock.uptimeMillis(), true); } mActivityTaskManager.stopSystemLockTaskMode(); - mShadeController.collapsePanel(false); + mShadeController.collapseShade(false); if (mTelecomManager != null && mTelecomManager.isInCall()) { mTelecomManager.showInCallScreen(false); if (mEmergencyButtonCallback != null) { diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java index 76a7cad15419..8ae63c4e2f54 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java @@ -17,24 +17,25 @@ package com.android.systemui; import android.app.AlertDialog; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.UserInfo; import android.os.UserHandle; -import android.util.Log; + +import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.settings.SecureSettings; +import java.util.concurrent.Executor; + import javax.inject.Inject; import dagger.assisted.Assisted; @@ -44,31 +45,66 @@ import dagger.assisted.AssistedInject; /** * Manages notification when a guest session is resumed. */ -public class GuestResumeSessionReceiver extends BroadcastReceiver { - - private static final String TAG = GuestResumeSessionReceiver.class.getSimpleName(); +public class GuestResumeSessionReceiver { @VisibleForTesting public static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in"; @VisibleForTesting public AlertDialog mNewSessionDialog; + private final Executor mMainExecutor; private final UserTracker mUserTracker; private final SecureSettings mSecureSettings; - private final BroadcastDispatcher mBroadcastDispatcher; private final ResetSessionDialog.Factory mResetSessionDialogFactory; private final GuestSessionNotification mGuestSessionNotification; + @VisibleForTesting + public final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + cancelDialog(); + + UserInfo currentUser = mUserTracker.getUserInfo(); + if (!currentUser.isGuest()) { + return; + } + + int guestLoginState = mSecureSettings.getIntForUser( + SETTING_GUEST_HAS_LOGGED_IN, 0, newUser); + + if (guestLoginState == 0) { + // set 1 to indicate, 1st login + guestLoginState = 1; + mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, + newUser); + } else if (guestLoginState == 1) { + // set 2 to indicate, 2nd or later login + guestLoginState = 2; + mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, + newUser); + } + + mGuestSessionNotification.createPersistentNotification(currentUser, + (guestLoginState <= 1)); + + if (guestLoginState > 1) { + mNewSessionDialog = mResetSessionDialogFactory.create(newUser); + mNewSessionDialog.show(); + } + } + }; + @Inject public GuestResumeSessionReceiver( + @Main Executor mainExecutor, UserTracker userTracker, SecureSettings secureSettings, - BroadcastDispatcher broadcastDispatcher, GuestSessionNotification guestSessionNotification, ResetSessionDialog.Factory resetSessionDialogFactory) { + mMainExecutor = mainExecutor; mUserTracker = userTracker; mSecureSettings = secureSettings; - mBroadcastDispatcher = broadcastDispatcher; mGuestSessionNotification = guestSessionNotification; mResetSessionDialogFactory = resetSessionDialogFactory; } @@ -77,49 +113,7 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver { * Register this receiver with the {@link BroadcastDispatcher} */ public void register() { - IntentFilter f = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(this, f, null /* handler */, UserHandle.SYSTEM); - } - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - cancelDialog(); - - int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId == UserHandle.USER_NULL) { - Log.e(TAG, intent + " sent to " + TAG + " without EXTRA_USER_HANDLE"); - return; - } - - UserInfo currentUser = mUserTracker.getUserInfo(); - if (!currentUser.isGuest()) { - return; - } - - int guestLoginState = mSecureSettings.getIntForUser( - SETTING_GUEST_HAS_LOGGED_IN, 0, userId); - - if (guestLoginState == 0) { - // set 1 to indicate, 1st login - guestLoginState = 1; - mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId); - } else if (guestLoginState == 1) { - // set 2 to indicate, 2nd or later login - guestLoginState = 2; - mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId); - } - - mGuestSessionNotification.createPersistentNotification(currentUser, - (guestLoginState <= 1)); - - if (guestLoginState > 1) { - mNewSessionDialog = mResetSessionDialogFactory.create(userId); - mNewSessionDialog.show(); - } - } + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); } private void cancelDialog() { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 7e3b1389792c..02a6d7be7143 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -26,10 +26,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import android.annotation.IdRes; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -45,7 +42,6 @@ import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Handler; import android.os.SystemProperties; import android.os.Trace; -import android.os.UserHandle; import android.provider.Settings.Secure; import android.util.DisplayUtils; import android.util.Log; @@ -68,7 +64,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.settingslib.Utils; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.decor.CutoutDecorProviderFactory; @@ -128,7 +123,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { private DisplayManager mDisplayManager; @VisibleForTesting protected boolean mIsRegistered; - private final BroadcastDispatcher mBroadcastDispatcher; private final Context mContext; private final Executor mMainExecutor; private final TunerService mTunerService; @@ -302,7 +296,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { public ScreenDecorations(Context context, @Main Executor mainExecutor, SecureSettings secureSettings, - BroadcastDispatcher broadcastDispatcher, TunerService tunerService, UserTracker userTracker, PrivacyDotViewController dotViewController, @@ -312,7 +305,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mContext = context; mMainExecutor = mainExecutor; mSecureSettings = secureSettings; - mBroadcastDispatcher = broadcastDispatcher; mTunerService = tunerService; mUserTracker = userTracker; mDotViewController = dotViewController; @@ -598,10 +590,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mColorInversionSetting.onChange(false); updateColorInversion(mColorInversionSetting.getValue()); - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter, - mExecutor, UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mExecutor); mIsRegistered = true; } else { mMainExecutor.execute(() -> mTunerService.removeTunable(this)); @@ -610,7 +599,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mColorInversionSetting.setListening(false); } - mBroadcastDispatcher.unregisterReceiver(mUserSwitchIntentReceiver); + mUserTracker.removeCallback(mUserChangedCallback); mIsRegistered = false; } } @@ -897,18 +886,18 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { } } - private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - int newUserId = mUserTracker.getUserId(); - if (DEBUG) { - Log.d(TAG, "UserSwitched newUserId=" + newUserId); - } - // update color inversion setting to the new user - mColorInversionSetting.setUserId(newUserId); - updateColorInversion(mColorInversionSetting.getValue()); - } - }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + if (DEBUG) { + Log.d(TAG, "UserSwitched newUserId=" + newUser); + } + // update color inversion setting to the new user + mColorInversionSetting.setUserId(newUser); + updateColorInversion(mColorInversionSetting.getValue()); + } + }; private void updateColorInversion(int colorsInvertedValue) { mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index 998288a0a2c0..dab73e9bf289 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -52,6 +52,7 @@ import com.android.internal.util.ScreenshotHelper; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.recents.Recents; +import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -183,15 +184,18 @@ public class SystemActions implements CoreStartable { private final AccessibilityManager mA11yManager; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; private final NotificationShadeWindowController mNotificationShadeController; + private final ShadeController mShadeController; private final StatusBarWindowCallback mNotificationShadeCallback; private boolean mDismissNotificationShadeActionRegistered; @Inject public SystemActions(Context context, NotificationShadeWindowController notificationShadeController, + ShadeController shadeController, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, Optional<Recents> recentsOptional) { mContext = context; + mShadeController = shadeController; mRecentsOptional = recentsOptional; mReceiver = new SystemActionsBroadcastReceiver(); mLocale = mContext.getResources().getConfiguration().getLocales().get(0); @@ -529,9 +533,7 @@ public class SystemActions implements CoreStartable { } private void handleAccessibilityDismissNotificationShade() { - mCentralSurfacesOptionalLazy.get().ifPresent( - centralSurfaces -> centralSurfaces.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_NONE, false /* force */)); + mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); } private void handleDpadUp() { diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt index 5616a00592f2..621b99d6804a 100644 --- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt @@ -29,13 +29,15 @@ import android.os.UserHandle import android.util.Log import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper +import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper import com.android.systemui.people.widget.PeopleBackupHelper /** * Helper for backing up elements in SystemUI * - * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. - * The helper can be used to back up any element that is stored in [Context.getFilesDir]. + * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The + * helper can be used to back up any element that is stored in [Context.getFilesDir] or + * [Context.getSharedPreferences]. * * After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0, * indicating that restoring is finished for a given user. @@ -47,9 +49,11 @@ open class BackupHelper : BackupAgentHelper() { internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite" private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences" + private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY = + "systemui.keyguard.quickaffordance.shared_preferences" val controlsDataLock = Any() const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED" - private const val PERMISSION_SELF = "com.android.systemui.permission.SELF" + const val PERMISSION_SELF = "com.android.systemui.permission.SELF" } override fun onCreate(userHandle: UserHandle, operationType: Int) { @@ -67,17 +71,27 @@ open class BackupHelper : BackupAgentHelper() { } val keys = PeopleBackupHelper.getFilesToBackup() - addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper( - this, userHandle, keys.toTypedArray())) + addHelper( + PEOPLE_TILES_BACKUP_KEY, + PeopleBackupHelper(this, userHandle, keys.toTypedArray()) + ) + addHelper( + KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY, + KeyguardQuickAffordanceBackupHelper( + context = this, + userId = userHandle.identifier, + ), + ) } override fun onRestoreFinished() { super.onRestoreFinished() - val intent = Intent(ACTION_RESTORE_FINISHED).apply { - `package` = packageName - putExtra(Intent.EXTRA_USER_ID, userId) - flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY - } + val intent = + Intent(ACTION_RESTORE_FINISHED).apply { + `package` = packageName + putExtra(Intent.EXTRA_USER_ID, userId) + flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY + } sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF) } @@ -90,7 +104,9 @@ open class BackupHelper : BackupAgentHelper() { * @property lock a lock to hold while backing up and restoring the files. * @property context the context of the [BackupAgent] * @property fileNamesAndPostProcess a map from the filenames to back up and the post processing + * ``` * actions to take + * ``` */ private class NoOverwriteFileBackupHelper( val lock: Any, @@ -115,23 +131,23 @@ open class BackupHelper : BackupAgentHelper() { data: BackupDataOutput?, newState: ParcelFileDescriptor? ) { - synchronized(lock) { - super.performBackup(oldState, data, newState) - } + synchronized(lock) { super.performBackup(oldState, data, newState) } } } } + private fun getPPControlsFile(context: Context): () -> Unit { return { val filesDir = context.filesDir val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS) if (file.exists()) { - val dest = Environment.buildPath(filesDir, - AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME) + val dest = + Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME) file.copyTo(dest) val jobScheduler = context.getSystemService(JobScheduler::class.java) jobScheduler?.schedule( - AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)) + AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context) + ) } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt index b2a2a679b383..b962cc43eddf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -107,6 +107,8 @@ open class AuthBiometricFingerprintIconController( if (shouldAnimateForTransition(lastState, newState)) { iconView.playAnimation() iconViewOverlay.playAnimation() + } else if (lastState == STATE_IDLE && newState == STATE_AUTHENTICATING_ANIMATING_IN) { + iconView.playAnimation() } LottieColorUtils.applyDynamicColors(context, iconView) LottieColorUtils.applyDynamicColors(context, iconViewOverlay) diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 537cbc5a267d..a0a892de0085 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -64,8 +64,9 @@ private const val TAG = "BroadcastDispatcher" * from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for * a given broadcast. * - * Use only for IntentFilters with actions and optionally categories. It does not support, - * permissions, schemes, data types, data authorities or priority different than 0. + * Use only for IntentFilters with actions and optionally categories. It does not support schemes, + * data types, data authorities or priority different than 0. + * * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery). * Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui * and doesn't need to worry about being killed. diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt index 4dfcd6398a4d..66e5d7c4a3bc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt @@ -30,6 +30,7 @@ import android.os.UserHandle import android.service.controls.ControlsProviderService import androidx.annotation.WorkerThread import com.android.settingslib.applications.DefaultAppInfo +import com.android.systemui.R import java.util.Objects class ControlsServiceInfo( @@ -59,7 +60,8 @@ class ControlsServiceInfo( * instead of using the controls rendered by SystemUI. * * The activity must be in the same package, exported, enabled and protected by the - * [Manifest.permission.BIND_CONTROLS] permission. + * [Manifest.permission.BIND_CONTROLS] permission. Additionally, only packages declared in + * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel. */ var panelActivity: ComponentName? = null private set @@ -70,6 +72,9 @@ class ControlsServiceInfo( fun resolvePanelActivity() { if (resolved) return resolved = true + val validPackages = context.resources + .getStringArray(R.array.config_controlsPreferredPackages) + if (componentName.packageName !in validPackages) return panelActivity = _panelActivity?.let { val resolveInfos = mPm.queryIntentActivitiesAsUser( Intent().setComponent(it), diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index bdfe1fbeb4b5..80c5f661f9a3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -33,7 +33,6 @@ import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.backup.BackupHelper -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController @@ -60,11 +59,10 @@ class ControlsControllerImpl @Inject constructor ( private val uiController: ControlsUiController, private val bindingController: ControlsBindingController, private val listingController: ControlsListingController, - private val broadcastDispatcher: BroadcastDispatcher, private val userFileManager: UserFileManager, + private val userTracker: UserTracker, optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpManager: DumpManager, - userTracker: UserTracker ) : Dumpable, ControlsController { companion object { @@ -121,18 +119,15 @@ class ControlsControllerImpl @Inject constructor ( userChanging = false } - private val userSwitchReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == Intent.ACTION_USER_SWITCHED) { - userChanging = true - val newUser = - UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId)) - if (currentUser == newUser) { - userChanging = false - return - } - setValuesForUser(newUser) + private val userTrackerCallback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + userChanging = true + val newUserHandle = UserHandle.of(newUser) + if (currentUser == newUserHandle) { + userChanging = false + return } + setValuesForUser(newUserHandle) } } @@ -234,12 +229,7 @@ class ControlsControllerImpl @Inject constructor ( dumpManager.registerDumpable(javaClass.name, this) resetFavorites() userChanging = false - broadcastDispatcher.registerReceiver( - userSwitchReceiver, - IntentFilter(Intent.ACTION_USER_SWITCHED), - executor, - UserHandle.ALL - ) + userTracker.addCallback(userTrackerCallback, executor) context.registerReceiver( restoreFinishedReceiver, IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), @@ -251,7 +241,7 @@ class ControlsControllerImpl @Inject constructor ( } fun destroy() { - broadcastDispatcher.unregisterReceiver(userSwitchReceiver) + userTracker.removeCallback(userTrackerCallback) context.unregisterReceiver(restoreFinishedReceiver) listingController.removeCallback(listingCallback) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java index d60a22204b3d..3d8e4cb71aca 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java @@ -19,7 +19,6 @@ package com.android.systemui.dagger; import android.content.BroadcastReceiver; import com.android.systemui.GuestResetOrExitSessionReceiver; -import com.android.systemui.GuestResumeSessionReceiver; import com.android.systemui.media.dialog.MediaOutputDialogReceiver; import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; @@ -106,15 +105,6 @@ public abstract class DefaultBroadcastReceiverBinder { */ @Binds @IntoMap - @ClassKey(GuestResumeSessionReceiver.class) - public abstract BroadcastReceiver bindGuestResumeSessionReceiver( - GuestResumeSessionReceiver broadcastReceiver); - - /** - * - */ - @Binds - @IntoMap @ClassKey(GuestResetOrExitSessionReceiver.class) public abstract BroadcastReceiver bindGuestResetOrExitSessionReceiver( GuestResetOrExitSessionReceiver broadcastReceiver); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 0b69b80689e0..5daf1ceaf592 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -29,12 +29,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.display.AmbientDisplayConfiguration; import android.os.SystemClock; -import android.os.UserHandle; import android.text.format.Formatter; import android.util.IndentingPrintWriter; import android.util.Log; import android.view.Display; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.logging.UiEvent; @@ -100,6 +101,7 @@ public class DozeTriggers implements DozeMachine.Part { private final BroadcastDispatcher mBroadcastDispatcher; private final AuthController mAuthController; private final KeyguardStateController mKeyguardStateController; + private final UserTracker mUserTracker; private final UiEventLogger mUiEventLogger; private long mNotificationPulseTime; @@ -110,6 +112,14 @@ public class DozeTriggers implements DozeMachine.Part { private boolean mWantTouchScreenSensors; private boolean mWantSensors; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mDozeSensors.onUserSwitched(); + } + }; + @VisibleForTesting public enum DozingUpdateUiEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "Dozing updated due to notification.") @@ -210,6 +220,7 @@ public class DozeTriggers implements DozeMachine.Part { mAuthController = authController; mUiEventLogger = uiEventLogger; mKeyguardStateController = keyguardStateController; + mUserTracker = userTracker; } @Override @@ -234,7 +245,7 @@ public class DozeTriggers implements DozeMachine.Part { return; } mNotificationPulseTime = SystemClock.elapsedRealtime(); - if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) { + if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) { runIfNotNull(onPulseSuppressedListener); mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled"); return; @@ -490,12 +501,14 @@ public class DozeTriggers implements DozeMachine.Part { mBroadcastReceiver.register(mBroadcastDispatcher); mDockManager.addListener(mDockEventListener); mDozeHost.addCallback(mHostCallback); + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); } private void unregisterCallbacks() { mBroadcastReceiver.unregister(mBroadcastDispatcher); mDozeHost.removeCallback(mHostCallback); mDockManager.removeListener(mDockEventListener); + mUserTracker.removeCallback(mUserChangedCallback); } private void stopListeningToAllTriggers() { @@ -620,9 +633,6 @@ public class DozeTriggers implements DozeMachine.Part { requestPulse(DozeLog.PULSE_REASON_INTENT, false, /* performedProxCheck */ null /* onPulseSuppressedListener */); } - if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { - mDozeSensors.onUserSwitched(); - } } public void register(BroadcastDispatcher broadcastDispatcher) { @@ -630,7 +640,6 @@ public class DozeTriggers implements DozeMachine.Part { return; } IntentFilter filter = new IntentFilter(PULSE_ACTION); - filter.addAction(Intent.ACTION_USER_SWITCHED); broadcastDispatcher.registerReceiver(this, filter); mRegistered = true; } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index a70b791691ed..db19749d06de 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -348,6 +348,12 @@ object Flags { // TODO(b/256873975): Tracking Bug @JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar") + // TODO(b/260271148): Tracking bug + @Keep + @JvmField + val WM_DESKTOP_WINDOWING_2 = + sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false) + // 1200 - predictive back @Keep @JvmField @@ -368,6 +374,11 @@ object Flags { @JvmField val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = false) + // TODO(b/255854141): Tracking Bug + @JvmField + val WM_ENABLE_PREDICTIVE_BACK_SYSUI = + unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = false) + // 1300 - screenshots // TODO(b/254512719): Tracking Bug @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt index 29febb6dd0d9..4ae37c51f278 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt @@ -29,7 +29,7 @@ import android.util.Log import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract import javax.inject.Inject import kotlinx.coroutines.runBlocking diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 8846bbd4754d..c332a0d66294 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -249,6 +249,7 @@ public class KeyguardService extends Service { synchronized (mFinishCallbacks) { if (mFinishCallbacks.remove(transition) == null) return; } + info.releaseAllSurfaces(); Slog.d(TAG, "Finish IRemoteAnimationRunner."); finishCallback.onTransitionFinished(null /* wct */, null /* t */); } @@ -264,6 +265,8 @@ public class KeyguardService extends Service { synchronized (mFinishCallbacks) { origFinishCB = mFinishCallbacks.remove(transition); } + info.releaseAllSurfaces(); + t.close(); if (origFinishCB == null) { // already finished (or not started yet), so do nothing. return; @@ -459,12 +462,15 @@ public class KeyguardService extends Service { t.apply(); mBinder.setOccluded(true /* isOccluded */, true /* animate */); finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + info.releaseAllSurfaces(); } @Override public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) { + t.close(); + info.releaseAllSurfaces(); } }; @@ -476,12 +482,15 @@ public class KeyguardService extends Service { t.apply(); mBinder.setOccluded(false /* isOccluded */, true /* animate */); finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + info.releaseAllSurfaces(); } @Override public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) { + t.close(); + info.releaseAllSurfaces(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8403fe650687..6ed555056cb1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -869,7 +869,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onLaunchAnimationEnd(boolean launchIsFullScreen) { if (launchIsFullScreen) { - mCentralSurfaces.instantCollapseNotificationPanel(); + mShadeController.get().instantCollapseShade(); } mOccludeAnimationPlaying = false; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index f5220b8fae92..73dbeab3030b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -25,6 +25,7 @@ package com.android.systemui.keyguard.data.quickaffordance object BuiltInKeyguardQuickAffordanceKeys { // Please keep alphabetical order of const names to simplify future maintenance. const val CAMERA = "camera" + const val FLASHLIGHT = "flashlight" const val HOME_CONTROLS = "home" const val QR_CODE_SCANNER = "qr_code_scanner" const val QUICK_ACCESS_WALLET = "wallet" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt index 3c09aab60443..dbc376e62950 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt @@ -26,14 +26,17 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import dagger.Lazy +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject @SysUISingleton -class CameraQuickAffordanceConfig @Inject constructor( - @Application private val context: Context, - private val cameraGestureHelper: CameraGestureHelper, +class CameraQuickAffordanceConfig +@Inject +constructor( + @Application private val context: Context, + private val cameraGestureHelper: Lazy<CameraGestureHelper>, ) : KeyguardQuickAffordanceConfig { override val key: String @@ -46,17 +49,23 @@ class CameraQuickAffordanceConfig @Inject constructor( get() = com.android.internal.R.drawable.perm_group_camera override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> - get() = flowOf( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = Icon.Resource( + get() = + flowOf( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = + Icon.Resource( com.android.internal.R.drawable.perm_group_camera, ContentDescription.Resource(R.string.accessibility_camera_button) - ) + ) + ) ) - ) - override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult { - cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + cameraGestureHelper + .get() + .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt new file mode 100644 index 000000000000..49527d32d229 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.content.Context +import com.android.systemui.R +import com.android.systemui.animation.Expandable +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.statusbar.policy.FlashlightController +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +@SysUISingleton +class FlashlightQuickAffordanceConfig @Inject constructor( + @Application private val context: Context, + private val flashlightController: FlashlightController, +) : KeyguardQuickAffordanceConfig { + + private sealed class FlashlightState { + + abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState + + object On: FlashlightState() { + override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.ic_flashlight_on, + ContentDescription.Resource(R.string.quick_settings_flashlight_label) + ), + ActivationState.Active + ) + } + + object OffAvailable: FlashlightState() { + override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.ic_flashlight_off, + ContentDescription.Resource(R.string.quick_settings_flashlight_label) + ), + ActivationState.Inactive + ) + } + + object Unavailable: FlashlightState() { + override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = + KeyguardQuickAffordanceConfig.LockScreenState.Hidden + } + } + + override val key: String + get() = BuiltInKeyguardQuickAffordanceKeys.FLASHLIGHT + + override val pickerName: String + get() = context.getString(R.string.quick_settings_flashlight_label) + + override val pickerIconResourceId: Int + get() = if (flashlightController.isEnabled) { + R.drawable.ic_flashlight_on + } else { + R.drawable.ic_flashlight_off + } + + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = + conflatedCallbackFlow { + val flashlightCallback = object : FlashlightController.FlashlightListener { + override fun onFlashlightChanged(enabled: Boolean) { + trySendWithFailureLogging( + if (enabled) { + FlashlightState.On.toLockScreenState() + } else { + FlashlightState.OffAvailable.toLockScreenState() + }, + TAG + ) + } + + override fun onFlashlightError() { + trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG) + } + + override fun onFlashlightAvailabilityChanged(available: Boolean) { + trySendWithFailureLogging( + if (!available) { + FlashlightState.Unavailable.toLockScreenState() + } else { + if (flashlightController.isEnabled) { + FlashlightState.On.toLockScreenState() + } else { + FlashlightState.OffAvailable.toLockScreenState() + } + }, + TAG + ) + } + } + + flashlightController.addCallback(flashlightCallback) + + awaitClose { + flashlightController.removeCallback(flashlightCallback) + } + } + + override fun onTriggered(expandable: Expandable?): + KeyguardQuickAffordanceConfig.OnTriggeredResult { + flashlightController + .setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + + override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState = + if (flashlightController.isAvailable) { + KeyguardQuickAffordanceConfig.PickerScreenState.Default + } else { + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice + } + + companion object { + private const val TAG = "FlashlightQuickAffordanceConfig" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index f7225a249eda..3013227c21c0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -26,6 +26,7 @@ object KeyguardDataQuickAffordanceModule { @Provides @ElementsIntoSet fun quickAffordanceConfigs( + flashlight: FlashlightQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, @@ -33,6 +34,7 @@ object KeyguardDataQuickAffordanceModule { ): Set<KeyguardQuickAffordanceConfig> { return setOf( camera, + flashlight, home, quickAccessWallet, qrCodeScanner, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt index 4477310dca41..98b1a731b82c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -21,7 +21,7 @@ import android.content.Intent import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.shared.quickaffordance.ActivationState -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract import kotlinx.coroutines.flow.Flow /** Defines interface that can act as data source for a single quick affordance model. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt index b29cf45cc709..4f37e5f389ee 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt @@ -18,9 +18,11 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context +import android.content.IntentFilter import android.content.SharedPreferences -import androidx.annotation.VisibleForTesting import com.android.systemui.R +import com.android.systemui.backup.BackupHelper +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -28,14 +30,18 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.onStart /** * Manages and provides access to the current "selections" of keyguard quick affordances, answering * the question "which affordances should the keyguard show?". */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardQuickAffordanceSelectionManager @Inject @@ -43,15 +49,10 @@ constructor( @Application context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, + broadcastDispatcher: BroadcastDispatcher, ) { - private val sharedPrefs: SharedPreferences - get() = - userFileManager.getSharedPreferences( - FILE_NAME, - Context.MODE_PRIVATE, - userTracker.userId, - ) + private var sharedPrefs: SharedPreferences = instantiateSharedPrefs() private val userId: Flow<Int> = conflatedCallbackFlow { val callback = @@ -78,21 +79,54 @@ constructor( } } + /** + * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an + * initial value. + */ + private val backupRestorationEvents: Flow<Unit> = + broadcastDispatcher.broadcastFlow( + filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), + flags = Context.RECEIVER_NOT_EXPORTED, + permission = BackupHelper.PERMISSION_SELF, + ) + /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */ val selections: Flow<Map<String, List<String>>> = - userId.flatMapLatest { - conflatedCallbackFlow { - val listener = - SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> - trySend(getSelections()) - } + combine( + userId, + backupRestorationEvents.onStart { + // We emit an initial event to make sure that the combine emits at least once, + // even + // if we never get a Backup & Restore restoration event (which is the most + // common + // case anyway as restoration really only happens on initial device setup). + emit(Unit) + } + ) { _, _ -> + } + .flatMapLatest { + conflatedCallbackFlow { + // We want to instantiate a new SharedPreferences instance each time either the + // user + // ID changes or we have a backup & restore restoration event. The reason is + // that + // our sharedPrefs instance needs to be replaced with a new one as it depends on + // the + // user ID and when the B&R job completes, the backing file is replaced but the + // existing instance still has a stale in-memory cache. + sharedPrefs = instantiateSharedPrefs() - sharedPrefs.registerOnSharedPreferenceChangeListener(listener) - send(getSelections()) + val listener = + SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> + trySend(getSelections()) + } - awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) } + sharedPrefs.registerOnSharedPreferenceChangeListener(listener) + send(getSelections()) + + awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) } + } } - } /** * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in @@ -144,9 +178,17 @@ constructor( sharedPrefs.edit().putString(key, value).apply() } + private fun instantiateSharedPrefs(): SharedPreferences { + return userFileManager.getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + userTracker.userId, + ) + } + companion object { private const val TAG = "KeyguardQuickAffordanceSelectionManager" - @VisibleForTesting const val FILE_NAME = "quick_affordance_selections" + const val FILE_NAME = "quick_affordance_selections" private const val KEY_PREFIX_SLOT = "slot_" private const val SLOT_AFFORDANCES_DELIMITER = ":" private const val AFFORDANCE_DELIMITER = "," diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt new file mode 100644 index 000000000000..0e865cee0b76 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.backup + +import android.app.backup.SharedPreferencesBackupHelper +import android.content.Context +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.settings.UserFileManagerImpl + +/** Handles backup & restore for keyguard quick affordances. */ +class KeyguardQuickAffordanceBackupHelper( + context: Context, + userId: Int, +) : + SharedPreferencesBackupHelper( + context, + if (UserFileManagerImpl.isPrimaryUser(userId)) { + KeyguardQuickAffordanceSelectionManager.FILE_NAME + } else { + UserFileManagerImpl.secondaryUserFile( + context = context, + fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME, + directoryName = UserFileManagerImpl.SHARED_PREFS, + userId = userId, + ) + .also { UserFileManagerImpl.ensureParentDirExists(it) } + .toString() + } + ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 2d94d760cb54..ee7154ff7219 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -34,8 +34,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentati import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt index c27bfa338df3..bb04b6b41a33 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt @@ -30,10 +30,11 @@ import kotlinx.coroutines.flow.Flow */ interface Diffable<T> { /** - * Finds the differences between [prevVal] and [this] and logs those diffs to [row]. + * Finds the differences between [prevVal] and this object and logs those diffs to [row]. * * Each implementer should determine which individual fields have changed between [prevVal] and - * [this], and only log the fields that have actually changed. This helps save buffer space. + * this object, and only log the fields that have actually changed. This helps save buffer + * space. * * For example, if: * - prevVal = Object(val1=100, val2=200, val3=300) @@ -42,6 +43,16 @@ interface Diffable<T> { * Then only the val3 change should be logged. */ fun logDiffs(prevVal: T, row: TableRowLogger) + + /** + * Logs all the relevant fields of this object to [row]. + * + * As opposed to [logDiffs], this method should log *all* fields. + * + * Implementation is optional. This method will only be used with [logDiffsForTable] in order to + * fully log the initial value of the flow. + */ + fun logFull(row: TableRowLogger) {} } /** @@ -57,8 +68,35 @@ fun <T : Diffable<T>> Flow<T>.logDiffsForTable( columnPrefix: String, initialValue: T, ): Flow<T> { - return this.pairwiseBy(initialValue) { prevVal, newVal -> + // Fully log the initial value to the table. + val getInitialValue = { + tableLogBuffer.logChange(columnPrefix) { row -> initialValue.logFull(row) } + initialValue + } + return this.pairwiseBy(getInitialValue) { prevVal: T, newVal: T -> tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal) newVal } } + +/** + * Each time the boolean flow is updated with a new value that's different from the previous value, + * logs the new value to the given [tableLogBuffer]. + */ +fun Flow<Boolean>.logDiffsForTable( + tableLogBuffer: TableLogBuffer, + columnPrefix: String, + columnName: String, + initialValue: Boolean, +): Flow<Boolean> { + val initialValueFun = { + tableLogBuffer.logChange(columnPrefix, columnName, initialValue) + initialValue + } + return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean -> + if (prevVal != newVal) { + tableLogBuffer.logChange(columnPrefix, columnName, newVal) + } + newVal + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt index 429637a0ee4d..9d0b833d26cc 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt @@ -16,10 +16,7 @@ package com.android.systemui.log.table -import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable -import com.android.systemui.dump.DumpManager -import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.plugins.util.RingBuffer import com.android.systemui.util.time.SystemClock import java.io.PrintWriter @@ -83,7 +80,7 @@ class TableLogBuffer( maxSize: Int, private val name: String, private val systemClock: SystemClock, -) { +) : Dumpable { init { if (maxSize <= 0) { throw IllegalArgumentException("maxSize must be > 0") @@ -104,6 +101,9 @@ class TableLogBuffer( * @param columnPrefix a prefix that will be applied to every column name that gets logged. This * ensures that all the columns related to the same state object will be grouped together in the * table. + * + * @throws IllegalArgumentException if [columnPrefix] or column name contain "|". "|" is used as + * the separator token for parsing, so it can't be present in any part of the column name. */ @Synchronized fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) { @@ -113,6 +113,25 @@ class TableLogBuffer( newVal.logDiffs(prevVal, row) } + /** + * Logs change(s) to the buffer using [rowInitializer]. + * + * @param rowInitializer a function that will be called immediately to store relevant data on + * the row. + */ + @Synchronized + fun logChange(columnPrefix: String, rowInitializer: (TableRowLogger) -> Unit) { + val row = tempRow + row.timestamp = systemClock.currentTimeMillis() + row.columnPrefix = columnPrefix + rowInitializer(row) + } + + /** Logs a boolean change. */ + fun logChange(prefix: String, columnName: String, value: Boolean) { + logChange(systemClock.currentTimeMillis(), prefix, columnName, value) + } + // Keep these individual [logChange] methods private (don't let clients give us their own // timestamps.) @@ -135,32 +154,31 @@ class TableLogBuffer( @Synchronized private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange { + verifyValidName(prefix, columnName) val tableChange = buffer.advance() tableChange.reset(timestamp, prefix, columnName) return tableChange } - /** - * Registers this buffer as dumpables in [dumpManager]. Must be called for the table to be - * dumped. - * - * This will be automatically called in [TableLogBufferFactory.create]. - */ - fun registerDumpables(dumpManager: DumpManager) { - dumpManager.registerNormalDumpable("$name-changes", changeDumpable) - dumpManager.registerNormalDumpable("$name-table", tableDumpable) + private fun verifyValidName(prefix: String, columnName: String) { + if (prefix.contains(SEPARATOR)) { + throw IllegalArgumentException("columnPrefix cannot contain $SEPARATOR but was $prefix") + } + if (columnName.contains(SEPARATOR)) { + throw IllegalArgumentException( + "columnName cannot contain $SEPARATOR but was $columnName" + ) + } } - private val changeDumpable = Dumpable { pw, _ -> dumpChanges(pw) } - private val tableDumpable = Dumpable { pw, _ -> dumpTable(pw) } - - /** Dumps the list of [TableChange] objects. */ @Synchronized - @VisibleForTesting - fun dumpChanges(pw: PrintWriter) { + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println(HEADER_PREFIX + name) + pw.println("version $VERSION") for (i in 0 until buffer.size) { buffer[i].dump(pw) } + pw.println(FOOTER_PREFIX + name) } /** Dumps an individual [TableChange]. */ @@ -170,70 +188,14 @@ class TableLogBuffer( } val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp) pw.print(formattedTimestamp) - pw.print(" ") + pw.print(SEPARATOR) pw.print(this.getName()) - pw.print("=") + pw.print(SEPARATOR) pw.print(this.getVal()) pw.println() } /** - * Coalesces all the [TableChange] objects into a table of values of time and dumps the table. - */ - // TODO(b/259454430): Since this is an expensive process, it could cause the bug report dump to - // fail and/or not dump anything else. We should move this processing to ABT (Android Bug - // Tool), where we have unlimited time to process. - @Synchronized - @VisibleForTesting - fun dumpTable(pw: PrintWriter) { - val messages = buffer.iterator().asSequence().toList() - - if (messages.isEmpty()) { - return - } - - // Step 1: Create list of column headers - val headerSet = mutableSetOf<String>() - messages.forEach { headerSet.add(it.getName()) } - val headers: MutableList<String> = headerSet.toList().sorted().toMutableList() - headers.add(0, "timestamp") - - // Step 2: Create a list with the current values for each column. Will be updated with each - // change. - val currentRow: MutableList<String> = MutableList(headers.size) { DEFAULT_COLUMN_VALUE } - - // Step 3: For each message, make the correct update to [currentRow] and save it to [rows]. - val columnIndices: Map<String, Int> = - headers.mapIndexed { index, headerName -> headerName to index }.toMap() - val allRows = mutableListOf<List<String>>() - - messages.forEach { - if (!it.hasData()) { - return@forEach - } - - val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(it.timestamp) - if (formattedTimestamp != currentRow[0]) { - // The timestamp has updated, so save the previous row and continue to the next row - allRows.add(currentRow.toList()) - currentRow[0] = formattedTimestamp - } - val columnIndex = columnIndices[it.getName()]!! - currentRow[columnIndex] = it.getVal() - } - // Add the last row - allRows.add(currentRow.toList()) - - // Step 4: Dump the rows - DumpsysTableLogger( - name, - headers, - allRows, - ) - .printTableData(pw) - } - - /** * A private implementation of [TableRowLogger]. * * Used so that external clients can't modify [timestamp]. @@ -261,4 +223,8 @@ class TableLogBuffer( } val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) -private const val DEFAULT_COLUMN_VALUE = "UNKNOWN" + +private const val HEADER_PREFIX = "SystemUI StateChangeTableSection START: " +private const val FOOTER_PREFIX = "SystemUI StateChangeTableSection END: " +private const val SEPARATOR = "|" +private const val VERSION = "1" diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt index f1f906f46d2d..7a90a7470cd2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt @@ -34,7 +34,7 @@ constructor( maxSize: Int, ): TableLogBuffer { val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock) - tableBuffer.registerDumpables(dumpManager) + dumpManager.registerNormalDumpable(name, tableBuffer) return tableBuffer } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt index 4891297dbcf9..2d10b823f784 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt @@ -32,10 +32,12 @@ import com.android.systemui.Dumpable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT +import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils import com.android.systemui.util.time.SystemClock @@ -55,6 +57,8 @@ class MediaResumeListener constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, + private val userTracker: UserTracker, + @Main private val mainExecutor: Executor, @Background private val backgroundExecutor: Executor, private val tunerService: TunerService, private val mediaBrowserFactory: ResumeMediaBrowserFactory, @@ -77,18 +81,26 @@ constructor( private var currentUserId: Int = context.userId @VisibleForTesting - val userChangeReceiver = + val userUnlockReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_USER_UNLOCKED == intent.action) { - loadMediaResumptionControls() - } else if (Intent.ACTION_USER_SWITCHED == intent.action) { - currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) - loadSavedComponents() + val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) + if (userId == currentUserId) { + loadMediaResumptionControls() + } } } } + private val userTrackerCallback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + currentUserId = newUser + loadSavedComponents() + } + } + private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() { override fun addTrack( @@ -126,13 +138,13 @@ constructor( dumpManager.registerDumpable(TAG, this) val unlockFilter = IntentFilter() unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED) - unlockFilter.addAction(Intent.ACTION_USER_SWITCHED) broadcastDispatcher.registerReceiver( - userChangeReceiver, + userUnlockReceiver, unlockFilter, null, UserHandle.ALL ) + userTracker.addCallback(userTrackerCallback, mainExecutor) loadSavedComponents() } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index cbb670ebf02d..f7a9bc760caf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -799,6 +799,16 @@ constructor( } if ( + desiredLocation == LOCATION_QS && + previousLocation == LOCATION_LOCKSCREEN && + statusbarState == StatusBarState.SHADE + ) { + // This is an invalid transition, can happen when tapping on home control and the UMO + // while being on landscape orientation in tablet. + return false + } + + if ( statusbarState == StatusBarState.KEYGUARD && (currentLocation == LOCATION_LOCKSCREEN || previousLocation == LOCATION_LOCKSCREEN) ) { @@ -1043,18 +1053,9 @@ constructor( rootOverlay!!.add(mediaFrame) } else { val targetHost = getHost(newLocation)!!.hostView - // When adding back to the host, let's make sure to reset the bounds. - // Usually adding the view will trigger a layout that does this automatically, - // but we sometimes suppress this. + // This will either do a full layout pass and remeasure, or it will bypass + // that and directly set the mediaFrame's bounds within the premeasured host. targetHost.addView(mediaFrame) - val left = targetHost.paddingLeft - val top = targetHost.paddingTop - mediaFrame.setLeftTopRightBottom( - left, - top, - left + currentBounds.width(), - top + currentBounds.height() - ) if (mediaFrame.childCount > 0) { val child = mediaFrame.getChildAt(0) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 4bf3031c02b4..4feb9844cb8b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -420,7 +420,9 @@ constructor( */ fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? = traceSection("MediaViewController#getMeasurementsForState") { - val viewState = obtainViewState(hostState) ?: return null + // measurements should never factor in the squish fraction + val viewState = + obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null measurement.measuredWidth = viewState.width measurement.measuredHeight = viewState.height return measurement diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index b91039d8d928..d762b39fa1af 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -52,13 +52,12 @@ import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransi import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay; import android.annotation.IdRes; +import android.annotation.NonNull; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.StatusBarManager; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.PixelFormat; @@ -114,7 +113,6 @@ import com.android.internal.view.AppearanceRegion; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; @@ -132,6 +130,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.recents.utilities.Utilities; @@ -202,7 +201,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final OverviewProxyService mOverviewProxyService; private final NavigationModeController mNavigationModeController; - private final BroadcastDispatcher mBroadcastDispatcher; + private final UserTracker mUserTracker; private final CommandQueue mCommandQueue; private final Optional<Pip> mPipOptional; private final Optional<Recents> mRecentsOptional; @@ -504,7 +503,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, SysUiState sysUiFlagsContainer, - BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker, CommandQueue commandQueue, Optional<Pip> pipOptional, Optional<Recents> recentsOptional, @@ -547,7 +546,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mNotificationRemoteInputManager = notificationRemoteInputManager; mOverviewProxyService = overviewProxyService; mNavigationModeController = navigationModeController; - mBroadcastDispatcher = broadcastDispatcher; + mUserTracker = userTracker; mCommandQueue = commandQueue; mPipOptional = pipOptional; mRecentsOptional = recentsOptional; @@ -729,9 +728,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements prepareNavigationBarView(); checkNavBarModes(); - IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, - Handler.getMain(), UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); notifyNavigationBarScreenOn(); @@ -782,7 +779,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mView.setUpdateActiveTouchRegionsCallback(null); getBarTransitions().destroy(); mOverviewProxyService.removeCallback(mOverviewProxyListener); - mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); + mUserTracker.removeCallback(mUserChangedCallback); mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); if (mOrientationHandle != null) { resetSecondaryHandle(); @@ -1674,21 +1671,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } }; - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // TODO(193941146): Currently unregistering a receiver through BroadcastDispatcher is - // async, but we've already cleared the fields. Just return early in this case. - if (mView == null) { - return; - } - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - // The accessibility settings may be different for the new user - updateAccessibilityStateFlags(); - } - } - }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + // The accessibility settings may be different for the new user + updateAccessibilityStateFlags(); + } + }; @VisibleForTesting int getNavigationIconHints() { diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 1da866efc08d..5a1ad96da7a9 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -39,6 +39,8 @@ import android.text.format.DateUtils; import android.util.Log; import android.util.Slog; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.utils.ThreadUtils; @@ -47,6 +49,7 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -80,6 +83,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { private final PowerManager mPowerManager; private final WarningsUI mWarnings; private final WakefulnessLifecycle mWakefulnessLifecycle; + private final UserTracker mUserTracker; private InattentiveSleepWarningView mOverlayView; private final Configuration mLastConfiguration = new Configuration(); private int mPlugType = 0; @@ -122,12 +126,21 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { } }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mWarnings.userSwitched(); + } + }; + @Inject public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher, CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, WarningsUI warningsUI, EnhancedEstimates enhancedEstimates, WakefulnessLifecycle wakefulnessLifecycle, - PowerManager powerManager) { + PowerManager powerManager, + UserTracker userTracker) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mCommandQueue = commandQueue; @@ -136,6 +149,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { mEnhancedEstimates = enhancedEstimates; mPowerManager = powerManager; mWakefulnessLifecycle = wakefulnessLifecycle; + mUserTracker = userTracker; } public void start() { @@ -154,6 +168,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { false, obs, UserHandle.USER_ALL); updateBatteryWarningLevels(); mReceiver.init(); + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); // Check to see if we need to let the user know that the phone previously shut down due @@ -250,7 +265,6 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler); // Force get initial values. Relying on Sticky behavior until API for getting info. if (!mHasReceivedBattery) { @@ -332,8 +346,6 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { plugged, bucket); }); - } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mWarnings.userSwitched(); } else { Slog.w(TAG, "unknown intent: " + intent); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 2ee5f05549cf..645b1256e5f1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -51,10 +51,13 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; + import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.util.leak.RotationUtils; @@ -76,6 +79,7 @@ public class ScreenPinningRequest implements View.OnClickListener, private final AccessibilityManager mAccessibilityService; private final WindowManager mWindowManager; private final BroadcastDispatcher mBroadcastDispatcher; + private final UserTracker mUserTracker; private RequestWindowView mRequestWindow; private int mNavBarMode; @@ -83,12 +87,21 @@ public class ScreenPinningRequest implements View.OnClickListener, /** ID of task to be pinned or locked. */ private int taskId; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + clearPrompt(); + } + }; + @Inject public ScreenPinningRequest( Context context, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, NavigationModeController navigationModeController, - BroadcastDispatcher broadcastDispatcher) { + BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker) { mContext = context; mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mAccessibilityService = (AccessibilityManager) @@ -97,6 +110,7 @@ public class ScreenPinningRequest implements View.OnClickListener, mContext.getSystemService(Context.WINDOW_SERVICE); mNavBarMode = navigationModeController.addListener(this); mBroadcastDispatcher = broadcastDispatcher; + mUserTracker = userTracker; } public void clearPrompt() { @@ -228,9 +242,9 @@ public class ScreenPinningRequest implements View.OnClickListener, } IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_SCREEN_OFF); mBroadcastDispatcher.registerReceiver(mReceiver, filter); + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); } private void inflateView(int rotation) { @@ -358,6 +372,7 @@ public class ScreenPinningRequest implements View.OnClickListener, @Override public void onDetachedFromWindow() { mBroadcastDispatcher.unregisterReceiver(mReceiver); + mUserTracker.removeCallback(mUserChangedCallback); } protected void onConfigurationChanged() { @@ -388,8 +403,7 @@ public class ScreenPinningRequest implements View.OnClickListener, public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) { post(mUpdateLayoutRunnable); - } else if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED) - || intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { clearPrompt(); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index ce4e0ecee914..b8684ee30b9a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -33,13 +33,16 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.CallbackController; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -55,8 +58,10 @@ public class RecordingController private boolean mIsRecording; private PendingIntent mStopIntent; private CountDownTimer mCountDownTimer = null; - private BroadcastDispatcher mBroadcastDispatcher; - private UserContextProvider mUserContextProvider; + private final Executor mMainExecutor; + private final BroadcastDispatcher mBroadcastDispatcher; + private final UserContextProvider mUserContextProvider; + private final UserTracker mUserTracker; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -66,12 +71,13 @@ public class RecordingController new CopyOnWriteArrayList<>(); @VisibleForTesting - protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - stopRecording(); - } - }; + final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + stopRecording(); + } + }; @VisibleForTesting protected final BroadcastReceiver mStateChangeReceiver = new BroadcastReceiver() { @@ -92,10 +98,14 @@ public class RecordingController * Create a new RecordingController */ @Inject - public RecordingController(BroadcastDispatcher broadcastDispatcher, - UserContextProvider userContextProvider) { + public RecordingController(@Main Executor mainExecutor, + BroadcastDispatcher broadcastDispatcher, + UserContextProvider userContextProvider, + UserTracker userTracker) { + mMainExecutor = mainExecutor; mBroadcastDispatcher = broadcastDispatcher; mUserContextProvider = userContextProvider; + mUserTracker = userTracker; } /** Create a dialog to show screen recording options to the user. */ @@ -139,9 +149,7 @@ public class RecordingController } try { startIntent.send(); - IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null, - UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE); mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null, @@ -211,7 +219,7 @@ public class RecordingController public synchronized void updateState(boolean isRecording) { if (!isRecording && mIsRecording) { // Unregister receivers if we have stopped recording - mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver); + mUserTracker.removeCallback(mUserChangedCallback); mBroadcastDispatcher.unregisterReceiver(mStateChangeReceiver); } mIsRecording = isRecording; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 8609e4af13f6..57b256e7b4a9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -84,6 +84,8 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import android.window.WindowContext; import androidx.concurrent.futures.CallbackToFutureAdapter; @@ -279,6 +281,13 @@ public class ScreenshotController { private final ActionIntentExecutor mActionExecutor; private final UserManager mUserManager; + private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { + if (DEBUG_INPUT) { + Log.d(TAG, "Predictive Back callback dispatched"); + } + respondToBack(); + }; + private ScreenshotView mScreenshotView; private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; @@ -465,6 +474,10 @@ public class ScreenshotController { } } + private void respondToBack() { + dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); + } + /** * Update resources on configuration change. Reinflate for theme/color changes. */ @@ -476,6 +489,26 @@ public class ScreenshotController { // Inflate the screenshot layout mScreenshotView = (ScreenshotView) LayoutInflater.from(mContext).inflate(R.layout.screenshot, null); + mScreenshotView.addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(@NonNull View v) { + if (DEBUG_INPUT) { + Log.d(TAG, "Registering Predictive Back callback"); + } + mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback); + } + + @Override + public void onViewDetachedFromWindow(@NonNull View v) { + if (DEBUG_INPUT) { + Log.d(TAG, "Unregistering Predictive Back callback"); + } + mScreenshotView.findOnBackInvokedDispatcher() + .unregisterOnBackInvokedCallback(mOnBackInvokedCallback); + } + }); mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() { @Override public void onUserInteraction() { @@ -503,7 +536,7 @@ public class ScreenshotController { if (DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK"); } - dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); + respondToBack(); return true; } return false; @@ -972,13 +1005,8 @@ public class ScreenshotController { if (imageData.uri != null) { if (!imageData.owner.equals(Process.myUserHandle())) { - // TODO: Handle non-primary user ownership (e.g. Work Profile) - // This image is owned by another user. Special treatment will be - // required in the UI (badging) as well as sending intents which can - // correctly forward those URIs on to be read (actions). - - Log.d(TAG, "*** Screenshot saved to a non-primary user (" - + imageData.owner + ") as " + imageData.uri); + Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as " + + imageData.uri); } mScreenshotHandler.post(() -> { if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { @@ -1059,6 +1087,11 @@ public class ScreenshotController { R.string.screenshot_failed_to_save_text); } else { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName); + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) + && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0, + mPackageName); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt index d450afa59c7d..bfba6dfddfac 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt @@ -35,12 +35,14 @@ import java.io.File import javax.inject.Inject /** - * Implementation for retrieving file paths for file storage of system and secondary users. - * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user. - * For system user, we use the conventional {File Directory} + * Implementation for retrieving file paths for file storage of system and secondary users. Files + * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the + * conventional {File Directory} */ @SysUISingleton -class UserFileManagerImpl @Inject constructor( +class UserFileManagerImpl +@Inject +constructor( // Context of system process and system user. private val context: Context, val userManager: UserManager, @@ -49,80 +51,114 @@ class UserFileManagerImpl @Inject constructor( ) : UserFileManager, CoreStartable { companion object { private const val FILES = "files" - @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs" + const val SHARED_PREFS = "shared_prefs" @VisibleForTesting internal const val ID = "UserFileManager" - } - private val broadcastReceiver = object : BroadcastReceiver() { + /** Returns `true` if the given user ID is that for the primary/system user. */ + fun isPrimaryUser(userId: Int): Boolean { + return UserHandle(userId).isSystem + } + /** - * Listen to Intent.ACTION_USER_REMOVED to clear user data. + * Returns a [File] pointing to the correct path for a secondary user ID. + * + * Note that there is no check for the type of user. This should only be called for + * secondary users, never for the system user. For that, make sure to call [isPrimaryUser]. + * + * Note also that there is no guarantee that the parent directory structure for the file + * exists on disk. For that, call [ensureParentDirExists]. + * + * @param context The context + * @param fileName The name of the file + * @param directoryName The name of the directory that would contain the file + * @param userId The ID of the user to build a file path for */ - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == Intent.ACTION_USER_REMOVED) { - clearDeletedUserData() + fun secondaryUserFile( + context: Context, + fileName: String, + directoryName: String, + userId: Int, + ): File { + return Environment.buildPath( + context.filesDir, + ID, + userId.toString(), + directoryName, + fileName, + ) + } + + /** + * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs + * recursively. + */ + fun ensureParentDirExists(file: File) { + val parent = file.parentFile + if (!parent.exists()) { + if (!parent.mkdirs()) { + Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}") + } } } } - /** - * Poll for user-specific directories to delete upon start up. - */ + private val broadcastReceiver = + object : BroadcastReceiver() { + /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */ + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_USER_REMOVED) { + clearDeletedUserData() + } + } + } + + /** Poll for user-specific directories to delete upon start up. */ override fun start() { clearDeletedUserData() - val filter = IntentFilter().apply { - addAction(Intent.ACTION_USER_REMOVED) - } + val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) } broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor) } - /** - * Return the file based on current user. - */ + /** Return the file based on current user. */ override fun getFile(fileName: String, userId: Int): File { - return if (UserHandle(userId).isSystem) { - Environment.buildPath( - context.filesDir, - fileName - ) + return if (isPrimaryUser(userId)) { + Environment.buildPath(context.filesDir, fileName) } else { - val secondaryFile = Environment.buildPath( - context.filesDir, - ID, - userId.toString(), - FILES, - fileName - ) + val secondaryFile = + secondaryUserFile( + context = context, + userId = userId, + directoryName = FILES, + fileName = fileName, + ) ensureParentDirExists(secondaryFile) secondaryFile } } - /** - * Get shared preferences from user. - */ + /** Get shared preferences from user. */ override fun getSharedPreferences( fileName: String, @Context.PreferencesMode mode: Int, userId: Int ): SharedPreferences { - if (UserHandle(userId).isSystem) { + if (isPrimaryUser(userId)) { return context.getSharedPreferences(fileName, mode) } - val secondaryUserDir = Environment.buildPath( - context.filesDir, - ID, - userId.toString(), - SHARED_PREFS, - fileName - ) + + val secondaryUserDir = + secondaryUserFile( + context = context, + fileName = fileName, + directoryName = SHARED_PREFS, + userId = userId, + ) ensureParentDirExists(secondaryUserDir) return context.getSharedPreferences(secondaryUserDir, mode) } - /** - * Remove dirs for deleted users. - */ + /** Remove dirs for deleted users. */ @VisibleForTesting internal fun clearDeletedUserData() { backgroundExecutor.execute { @@ -133,10 +169,11 @@ class UserFileManagerImpl @Inject constructor( dirsToDelete.forEach { dir -> try { - val dirToDelete = Environment.buildPath( - file, - dir, - ) + val dirToDelete = + Environment.buildPath( + file, + dir, + ) dirToDelete.deleteRecursively() } catch (e: Exception) { Log.e(ID, "Deletion failed.", e) @@ -144,18 +181,4 @@ class UserFileManagerImpl @Inject constructor( } } } - - /** - * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs - * recursively. - */ - @VisibleForTesting - internal fun ensureParentDirExists(file: File) { - val parent = file.parentFile - if (!parent.exists()) { - if (!parent.mkdirs()) { - Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}") - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 31e44646859f..5e47d6dd5b76 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -19,6 +19,7 @@ package com.android.systemui.shade import android.annotation.IdRes import android.app.StatusBarManager import android.content.res.Configuration +import android.os.Bundle import android.os.Trace import android.os.Trace.TRACE_TAG_APP import android.util.Pair @@ -34,6 +35,8 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.demomode.DemoMode +import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -53,6 +56,7 @@ import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER +import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.VariableDateView import com.android.systemui.statusbar.policy.VariableDateViewController @@ -89,7 +93,8 @@ class LargeScreenShadeHeaderController @Inject constructor( private val dumpManager: DumpManager, private val featureFlags: FeatureFlags, private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder, - private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager + private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager, + private val demoModeController: DemoModeController ) : ViewController<View>(header), Dumpable { companion object { @@ -126,7 +131,7 @@ class LargeScreenShadeHeaderController @Inject constructor( private lateinit var qsCarrierGroupController: QSCarrierGroupController private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon) - private val clock: TextView = header.findViewById(R.id.clock) + private val clock: Clock = header.findViewById(R.id.clock) private val date: TextView = header.findViewById(R.id.date) private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons) private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group) @@ -212,6 +217,14 @@ class LargeScreenShadeHeaderController @Inject constructor( view.onApplyWindowInsets(insets) } + private val demoModeReceiver = object : DemoMode { + override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK) + override fun dispatchDemoCommand(command: String, args: Bundle) = + clock.dispatchDemoCommand(command, args) + override fun onDemoModeStarted() = clock.onDemoModeStarted() + override fun onDemoModeFinished() = clock.onDemoModeFinished() + } + private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener { override fun onChipVisibilityRefreshed(visible: Boolean) { if (header is MotionLayout) { @@ -300,6 +313,7 @@ class LargeScreenShadeHeaderController @Inject constructor( dumpManager.registerDumpable(this) configurationController.addCallback(configurationControllerListener) + demoModeController.addCallback(demoModeReceiver) updateVisibility() updateTransition() @@ -309,6 +323,7 @@ class LargeScreenShadeHeaderController @Inject constructor( privacyIconsController.chipVisibilityListener = null dumpManager.unregisterDumpable(this::class.java.simpleName) configurationController.removeCallback(configurationControllerListener) + demoModeController.removeCallback(demoModeReceiver) } fun disable(state1: Int, state2: Int, animate: Boolean) { @@ -521,4 +536,7 @@ class LargeScreenShadeHeaderController @Inject constructor( updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges) } } + + @VisibleForTesting + internal fun simulateViewDetached() = this.onViewDetached() } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 629d3d4d50ba..e68182ef85c9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2072,13 +2072,6 @@ public final class NotificationPanelViewController implements Dumpable { mInitialTouchX = x; initVelocityTracker(); trackMovement(event); - float qsExpansionFraction = computeQsExpansionFraction(); - // Intercept the touch if QS is between fully collapsed and fully expanded state - if (qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) { - mShadeLog.logMotionEvent(event, - "onQsIntercept: down action, QS partially expanded/collapsed"); - return true; - } if (mKeyguardShowing && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { // Dragging down on the lockscreen statusbar should prohibit other interactions @@ -2331,13 +2324,6 @@ public final class NotificationPanelViewController implements Dumpable { if (!isFullyCollapsed()) { handleQsDown(event); } - // defer touches on QQS to shade while shade is collapsing. Added margin for error - // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS. - if (computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) { - mShadeLog.logMotionEvent(event, - "handleQsTouch: QQS touched while shade collapsing"); - mQsTracking = false; - } if (!mQsExpandImmediate && mQsTracking) { onQsTouch(event); if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) { @@ -2578,6 +2564,7 @@ public final class NotificationPanelViewController implements Dumpable { // Reset scroll position and apply that position to the expanded height. float height = mQsExpansionHeight; setQsExpansionHeight(height); + updateExpandedHeightToMaxHeight(); mNotificationStackScrollLayoutController.checkSnoozeLeavebehind(); // When expanding QS, let's authenticate the user if possible, diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index aa610bdcc90e..de9dcf99cde8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -16,6 +16,9 @@ package com.android.systemui.shade; +import android.view.MotionEvent; + +import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -29,31 +32,32 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; */ public interface ShadeController { - /** - * Make our window larger and the panel expanded - */ - void instantExpandNotificationsPanel(); + /** Make our window larger and the shade expanded */ + void instantExpandShade(); + + /** Collapse the shade instantly with no animation. */ + void instantCollapseShade(); + + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShade(); - /** See {@link #animateCollapsePanels(int, boolean)}. */ - void animateCollapsePanels(); + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShade(int flags); - /** See {@link #animateCollapsePanels(int, boolean)}. */ - void animateCollapsePanels(int flags); + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShadeForced(); + + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShadeDelayed(); /** * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or - * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}. + * dismissing status bar when on {@link StatusBarState#SHADE}. */ - void animateCollapsePanels(int flags, boolean force); - - /** See {@link #animateCollapsePanels(int, boolean)}. */ - void animateCollapsePanels(int flags, boolean force, boolean delayed); - - /** See {@link #animateCollapsePanels(int, boolean)}. */ void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor); /** - * If the notifications panel is not fully expanded, collapse it animated. + * If the shade is not fully expanded, collapse it animated. * * @return Seems to always return false */ @@ -77,9 +81,7 @@ public interface ShadeController { */ void addPostCollapseAction(Runnable action); - /** - * Run all of the runnables added by {@link #addPostCollapseAction}. - */ + /** Run all of the runnables added by {@link #addPostCollapseAction}. */ void runPostCollapseRunnables(); /** @@ -87,13 +89,48 @@ public interface ShadeController { * * @return true if the shade was open, else false */ - boolean collapsePanel(); + boolean collapseShade(); /** - * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse - * the panel. Post collapse runnables will be executed + * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse + * the shade. Post collapse runnables will be executed * * @param animate true to animate the collapse, false for instantaneous collapse */ - void collapsePanel(boolean animate); + void collapseShade(boolean animate); + + /** Makes shade expanded but not visible. */ + void makeExpandedInvisible(); + + /** Makes shade expanded and visible. */ + void makeExpandedVisible(boolean force); + + /** Returns whether the shade is expanded and visible. */ + boolean isExpandedVisible(); + + /** Handle status bar touch event. */ + void onStatusBarTouch(MotionEvent event); + + /** Sets the listener for when the visibility of the shade changes. */ + void setVisibilityListener(ShadeVisibilityListener listener); + + /** */ + void setNotificationPresenter(NotificationPresenter presenter); + + /** */ + void setNotificationShadeWindowViewController( + NotificationShadeWindowViewController notificationShadeWindowViewController); + + /** */ + void setNotificationPanelViewController( + NotificationPanelViewController notificationPanelViewController); + + /** Listens for shade visibility changes. */ + interface ShadeVisibilityListener { + /** Called when the visibility of the shade changes. */ + void visibilityChanged(boolean visible); + + /** Called when shade expanded and visible state changed. */ + void expandedVisibleChanged(boolean expandedVisible); + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index d783293b95d4..807e2e63156f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -16,9 +16,12 @@ package com.android.systemui.shade; +import android.content.ComponentCallbacks2; import android.util.Log; +import android.view.MotionEvent; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; @@ -27,11 +30,12 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowController; import java.util.ArrayList; -import java.util.Optional; import javax.inject.Inject; @@ -39,68 +43,81 @@ import dagger.Lazy; /** An implementation of {@link ShadeController}. */ @SysUISingleton -public class ShadeControllerImpl implements ShadeController { +public final class ShadeControllerImpl implements ShadeController { private static final String TAG = "ShadeControllerImpl"; private static final boolean SPEW = false; + private final int mDisplayId; + private final CommandQueue mCommandQueue; + private final KeyguardStateController mKeyguardStateController; + private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarStateController mStatusBarStateController; - protected final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final int mDisplayId; - protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; + private final StatusBarWindowController mStatusBarWindowController; + private final Lazy<AssistManager> mAssistManagerLazy; + private final Lazy<NotificationGutsManager> mGutsManager; private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>(); + private boolean mExpandedVisible; + + private NotificationPanelViewController mNotificationPanelViewController; + private NotificationPresenter mPresenter; + private NotificationShadeWindowViewController mNotificationShadeWindowViewController; + private ShadeVisibilityListener mShadeVisibilityListener; + @Inject public ShadeControllerImpl( CommandQueue commandQueue, + KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, - NotificationShadeWindowController notificationShadeWindowController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, + StatusBarWindowController statusBarWindowController, + NotificationShadeWindowController notificationShadeWindowController, WindowManager windowManager, - Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, - Lazy<AssistManager> assistManagerLazy + Lazy<AssistManager> assistManagerLazy, + Lazy<NotificationGutsManager> gutsManager ) { mCommandQueue = commandQueue; mStatusBarStateController = statusBarStateController; + mStatusBarWindowController = statusBarWindowController; + mGutsManager = gutsManager; mNotificationShadeWindowController = notificationShadeWindowController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mDisplayId = windowManager.getDefaultDisplay().getDisplayId(); - // TODO: Remove circular reference to CentralSurfaces when possible. - mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; + mKeyguardStateController = keyguardStateController; mAssistManagerLazy = assistManagerLazy; } @Override - public void instantExpandNotificationsPanel() { + public void instantExpandShade() { // Make our window larger and the panel expanded. - getCentralSurfaces().makeExpandedVisible(true /* force */); - getNotificationPanelViewController().expand(false /* animate */); + makeExpandedVisible(true /* force */); + mNotificationPanelViewController.expand(false /* animate */); mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */); } @Override - public void animateCollapsePanels() { - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + public void animateCollapseShade() { + animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); } @Override - public void animateCollapsePanels(int flags) { - animateCollapsePanels(flags, false /* force */, false /* delayed */, - 1.0f /* speedUpFactor */); + public void animateCollapseShade(int flags) { + animateCollapsePanels(flags, false, false, 1.0f); } @Override - public void animateCollapsePanels(int flags, boolean force) { - animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */); + public void animateCollapseShadeForced() { + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f); } @Override - public void animateCollapsePanels(int flags, boolean force, boolean delayed) { - animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */); + public void animateCollapseShadeDelayed() { + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f); } @Override @@ -111,34 +128,26 @@ public class ShadeControllerImpl implements ShadeController { return; } if (SPEW) { - Log.d(TAG, "animateCollapse():" - + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible() - + " flags=" + flags); + Log.d(TAG, + "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags); } - - // TODO(b/62444020): remove when this bug is fixed - Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView() - + " canPanelBeCollapsed(): " - + getNotificationPanelViewController().canPanelBeCollapsed()); if (getNotificationShadeWindowView() != null - && getNotificationPanelViewController().canPanelBeCollapsed() + && mNotificationPanelViewController.canPanelBeCollapsed() && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { // release focus immediately to kick off focus change transition mNotificationShadeWindowController.setNotificationShadeFocusable(false); - getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper(); - getNotificationPanelViewController() - .collapsePanel(true /* animate */, delayed, speedUpFactor); + mNotificationShadeWindowViewController.cancelExpandHelper(); + mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor); } } - @Override public boolean closeShadeIfOpen() { - if (!getNotificationPanelViewController().isFullyCollapsed()) { + if (!mNotificationPanelViewController.isFullyCollapsed()) { mCommandQueue.animateCollapsePanels( CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); - getCentralSurfaces().visibilityChanged(false); + notifyVisibilityChanged(false); mAssistManagerLazy.get().hideAssist(); } return false; @@ -146,21 +155,19 @@ public class ShadeControllerImpl implements ShadeController { @Override public boolean isShadeOpen() { - NotificationPanelViewController controller = - getNotificationPanelViewController(); - return controller.isExpanding() || controller.isFullyExpanded(); + return mNotificationPanelViewController.isExpanding() + || mNotificationPanelViewController.isFullyExpanded(); } @Override public void postOnShadeExpanded(Runnable executable) { - getNotificationPanelViewController().addOnGlobalLayoutListener( + mNotificationPanelViewController.addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - if (getCentralSurfaces().getNotificationShadeWindowView() - .isVisibleToUser()) { - getNotificationPanelViewController().removeOnGlobalLayoutListener(this); - getNotificationPanelViewController().postToView(executable); + if (getNotificationShadeWindowView().isVisibleToUser()) { + mNotificationPanelViewController.removeOnGlobalLayoutListener(this); + mNotificationPanelViewController.postToView(executable); } } }); @@ -183,12 +190,11 @@ public class ShadeControllerImpl implements ShadeController { } @Override - public boolean collapsePanel() { - if (!getNotificationPanelViewController().isFullyCollapsed()) { + public boolean collapseShade() { + if (!mNotificationPanelViewController.isFullyCollapsed()) { // close the shade if it was open - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed */); - getCentralSurfaces().visibilityChanged(false); + animateCollapseShadeDelayed(); + notifyVisibilityChanged(false); return true; } else { @@ -197,33 +203,131 @@ public class ShadeControllerImpl implements ShadeController { } @Override - public void collapsePanel(boolean animate) { + public void collapseShade(boolean animate) { if (animate) { - boolean willCollapse = collapsePanel(); + boolean willCollapse = collapseShade(); if (!willCollapse) { runPostCollapseRunnables(); } - } else if (!getPresenter().isPresenterFullyCollapsed()) { - getCentralSurfaces().instantCollapseNotificationPanel(); - getCentralSurfaces().visibilityChanged(false); + } else if (!mPresenter.isPresenterFullyCollapsed()) { + instantCollapseShade(); + notifyVisibilityChanged(false); } else { runPostCollapseRunnables(); } } - private CentralSurfaces getCentralSurfaces() { - return mCentralSurfacesOptionalLazy.get().get(); + @Override + public void onStatusBarTouch(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mExpandedVisible) { + animateCollapseShade(); + } + } } - private NotificationPresenter getPresenter() { - return getCentralSurfaces().getPresenter(); + @Override + public void instantCollapseShade() { + mNotificationPanelViewController.instantCollapse(); + runPostCollapseRunnables(); } - protected NotificationShadeWindowView getNotificationShadeWindowView() { - return getCentralSurfaces().getNotificationShadeWindowView(); + @Override + public void makeExpandedVisible(boolean force) { + if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); + if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { + return; + } + + mExpandedVisible = true; + + // Expand the window to encompass the full screen in anticipation of the drag. + // It's only possible to do atomically because the status bar is at the top of the screen! + mNotificationShadeWindowController.setPanelVisible(true); + + notifyVisibilityChanged(true); + mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); + notifyExpandedVisibleChanged(true); } - private NotificationPanelViewController getNotificationPanelViewController() { - return getCentralSurfaces().getNotificationPanelViewController(); + @Override + public void makeExpandedInvisible() { + if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible); + + if (!mExpandedVisible || getNotificationShadeWindowView() == null) { + return; + } + + // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) + mNotificationPanelViewController.collapsePanel(false, false, 1.0f); + + mNotificationPanelViewController.closeQs(); + + mExpandedVisible = false; + notifyVisibilityChanged(false); + + // Update the visibility of notification shade and status bar window. + mNotificationShadeWindowController.setPanelVisible(false); + mStatusBarWindowController.setForceStatusBarVisible(false); + + // Close any guts that might be visible + mGutsManager.get().closeAndSaveGuts( + true /* removeLeavebehind */, + true /* force */, + true /* removeControls */, + -1 /* x */, + -1 /* y */, + true /* resetMenu */); + + runPostCollapseRunnables(); + notifyExpandedVisibleChanged(false); + mCommandQueue.recomputeDisableFlags( + mDisplayId, + mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */); + + // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in + // the bouncer appear animation. + if (!mKeyguardStateController.isShowing()) { + WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + } + } + + @Override + public boolean isExpandedVisible() { + return mExpandedVisible; + } + + @Override + public void setVisibilityListener(ShadeVisibilityListener listener) { + mShadeVisibilityListener = listener; + } + + private void notifyVisibilityChanged(boolean visible) { + mShadeVisibilityListener.visibilityChanged(visible); + } + + private void notifyExpandedVisibleChanged(boolean expandedVisible) { + mShadeVisibilityListener.expandedVisibleChanged(expandedVisible); + } + + @Override + public void setNotificationPresenter(NotificationPresenter presenter) { + mPresenter = presenter; + } + + @Override + public void setNotificationShadeWindowViewController( + NotificationShadeWindowViewController controller) { + mNotificationShadeWindowViewController = controller; + } + + private NotificationShadeWindowView getNotificationShadeWindowView() { + return mNotificationShadeWindowViewController.getView(); + } + + @Override + public void setNotificationPanelViewController( + NotificationPanelViewController notificationPanelViewController) { + mNotificationPanelViewController = notificationPanelViewController; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index cdefae6b87f9..f4cd985adbdb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -30,6 +30,7 @@ import android.content.IntentSender; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -37,6 +38,7 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.NotificationVisibility; @@ -127,21 +129,6 @@ public class NotificationLockscreenUserManagerImpl implements public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { - case Intent.ACTION_USER_SWITCHED: - mCurrentUserId = intent.getIntExtra( - Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL); - updateCurrentProfilesCache(); - - Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); - - updateLockscreenNotificationSetting(); - updatePublicMode(); - mPresenter.onUserSwitched(mCurrentUserId); - - for (UserChangedListener listener : mListeners) { - listener.onUserChanged(mCurrentUserId); - } - break; case Intent.ACTION_USER_REMOVED: int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (removedUserId != -1) { @@ -181,6 +168,25 @@ public class NotificationLockscreenUserManagerImpl implements } }; + protected final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mCurrentUserId = newUser; + updateCurrentProfilesCache(); + + Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); + + updateLockscreenNotificationSetting(); + updatePublicMode(); + mPresenter.onUserSwitched(mCurrentUserId); + + for (UserChangedListener listener : mListeners) { + listener.onUserChanged(mCurrentUserId); + } + } + }; + protected final Context mContext; private final Handler mMainHandler; protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); @@ -284,7 +290,6 @@ public class NotificationLockscreenUserManagerImpl implements null /* handler */, UserHandle.ALL); IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_UNLOCKED); @@ -298,6 +303,8 @@ public class NotificationLockscreenUserManagerImpl implements mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null, Context.RECEIVER_EXPORTED_UNAUDITED); + mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler)); + mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late updateCurrentProfilesCache(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index e6dbcee10f60..7513aa7fa2a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -2,22 +2,20 @@ package com.android.systemui.statusbar.notification.interruption import android.app.Notification import android.app.Notification.VISIBILITY_SECRET -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent -import android.content.IntentFilter import android.database.ContentObserver import android.net.Uri import android.os.Handler +import android.os.HandlerExecutor import android.os.UserHandle import android.provider.Settings import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -78,7 +76,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val highPriorityProvider: HighPriorityProvider, private val statusBarStateController: SysuiStatusBarStateController, - private val broadcastDispatcher: BroadcastDispatcher, + private val userTracker: UserTracker, private val secureSettings: SecureSettings, private val globalSettings: GlobalSettings ) : CoreStartable, KeyguardNotificationVisibilityProvider { @@ -87,6 +85,15 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val onStateChangedListeners = ListenerSet<Consumer<String>>() private var hideSilentNotificationsOnLockscreen: Boolean = false + private val userTrackerCallback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + if (isLockedOrLocking) { + // maybe public mode changed + notifyStateChanged("onUserSwitched") + } + } + } + override fun start() { readShowSilentNotificationSetting() keyguardStateController.addCallback(object : KeyguardStateController.Callback { @@ -143,14 +150,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( notifyStateChanged("onStatusBarUpcomingStateChanged") } }) - broadcastDispatcher.registerReceiver(object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (isLockedOrLocking) { - // maybe public mode changed - notifyStateChanged(intent.action!!) - } - } - }, IntentFilter(Intent.ACTION_USER_SWITCHED)) + userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler)) } override fun addOnStateChangedListener(listener: Consumer<String>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java index 64f87cabaf74..b56bae12be6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java @@ -54,8 +54,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger; import com.android.systemui.statusbar.policy.HeadsUpManager; -import java.util.Collections; - import javax.inject.Inject; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 0ce9656a21b5..f21db0bde59a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -154,7 +154,7 @@ public class NotificationConversationInfo extends LinearLayout implements // If the user selected Priority and the previous selection was not priority, show a // People Tile add request. if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle()); } mGutsContainer.closeControls(v, /* save= */ true); 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 073bd4bf302b..b519aefcd4c9 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 @@ -1401,10 +1401,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mExpandedHeight = height; setIsExpanded(height > 0); int minExpansionHeight = getMinExpansionHeight(); - if (height < minExpansionHeight) { + if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) { mClipRect.left = 0; mClipRect.right = getWidth(); - mClipRect.top = getNotificationsClippingTopBound(); + mClipRect.top = 0; mClipRect.bottom = (int) height; height = minExpansionHeight; setRequestedClipBounds(mClipRect); @@ -1466,17 +1466,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable notifyAppearChangedListeners(); } - private int getNotificationsClippingTopBound() { - if (isHeadsUpTransition()) { - // HUN in split shade can go higher than bottom of NSSL when swiping up so we want - // to give it extra clipping margin. Because clipping has rounded corners, we also - // need to account for that corner clipping. - return -mAmbientState.getStackTopMargin() - mCornerRadius; - } else { - return 0; - } - } - private void notifyAppearChangedListeners() { float appear; float expandAmount; @@ -4236,7 +4225,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mShadeNeedsToClose = false; postDelayed( () -> { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); }, DELAY_BEFORE_SHADE_CLOSE /* delayMillis */); } @@ -5139,6 +5128,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable println(pw, "intrinsicPadding", mIntrinsicPadding); println(pw, "topPadding", mTopPadding); println(pw, "bottomPadding", mBottomPadding); + mNotificationStackSizeCalculator.dump(pw, args); }); pw.println(); pw.println("Contents:"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index ae854e2df91a..25f99c69d454 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.util.Compile import com.android.systemui.util.children +import java.io.PrintWriter import javax.inject.Inject import kotlin.math.max import kotlin.math.min @@ -53,6 +54,8 @@ constructor( @Main private val resources: Resources ) { + private lateinit var lastComputeHeightLog : String + /** * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf. * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space @@ -114,7 +117,9 @@ constructor( shelfIntrinsicHeight: Float ): Int { log { "\n" } - val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight) + + val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight, + /* computeHeight= */ false) var maxNotifications = stackHeightSequence.lastIndexWhile { heightResult -> @@ -157,18 +162,21 @@ constructor( shelfIntrinsicHeight: Float ): Float { log { "\n" } + lastComputeHeightLog = "" val heightPerMaxNotifications = - computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight) + computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight, + /* computeHeight= */ true) val (notificationsHeight, shelfHeightWithSpaceBefore) = heightPerMaxNotifications.elementAtOrElse(maxNotifications) { heightPerMaxNotifications.last() // Height with all notifications visible. } - log { - "computeHeight(maxNotifications=$maxNotifications," + + lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," + "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " + "${notificationsHeight + shelfHeightWithSpaceBefore}" + " = ($notificationsHeight + $shelfHeightWithSpaceBefore)" + log { + lastComputeHeightLog } return notificationsHeight + shelfHeightWithSpaceBefore } @@ -184,7 +192,8 @@ constructor( private fun computeHeightPerNotificationLimit( stack: NotificationStackScrollLayout, - shelfHeight: Float + shelfHeight: Float, + computeHeight: Boolean ): Sequence<StackHeight> = sequence { log { "computeHeightPerNotificationLimit" } @@ -213,9 +222,14 @@ constructor( currentIndex = firstViewInShelfIndex) spaceBeforeShelf + shelfHeight } + + val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " + + "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore" + if (computeHeight) { + lastComputeHeightLog += "\n" + currentLog + } log { - "i=$i notificationsHeight=$notifications " + - "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore" + currentLog } yield( StackHeight( @@ -260,6 +274,10 @@ constructor( return size } + fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog") + } + private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean { if (visibility == GONE || hasNoContentHeight()) return false if (onLockscreen) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 3557b4ab34f5..883ce1e715af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -191,8 +191,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void animateExpandSettingsPanel(@Nullable String subpanel); - void animateCollapsePanels(int flags, boolean force); - void collapsePanelOnMainThread(); void togglePanel(); @@ -280,8 +278,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void postAnimateOpenPanels(); - boolean isExpandedVisible(); - boolean isPanelExpanded(); void onInputFocusTransfer(boolean start, boolean cancel, float velocity); @@ -493,12 +489,13 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void updateNotificationPanelTouchState(); + /** + * TODO(b/257041702) delete this + * @deprecated Use ShadeController#makeExpandedVisible + */ + @Deprecated void makeExpandedVisible(boolean force); - void instantCollapseNotificationPanel(); - - void visibilityChanged(boolean visible); - int getDisplayId(); int getRotation(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 9e5a66f1e306..72ada0e17a01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -209,7 +209,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba public void animateExpandNotificationsPanel() { if (CentralSurfaces.SPEW) { Log.d(CentralSurfaces.TAG, - "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible()); + "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible()); } if (!mCommandQueue.panelsEnabled()) { return; @@ -222,7 +222,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba public void animateExpandSettingsPanel(@Nullable String subPanel) { if (CentralSurfaces.SPEW) { Log.d(CentralSurfaces.TAG, - "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible()); + "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible()); } if (!mCommandQueue.panelsEnabled()) { return; @@ -276,7 +276,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -293,7 +293,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { mCentralSurfaces.updateQsExpansionEnabled(); if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -550,7 +550,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba @Override public void togglePanel() { if (mCentralSurfaces.isPanelExpanded()) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } else { animateExpandNotificationsPanel(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index bc2ee1fd4f7f..1c0febb6a0ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -58,7 +58,6 @@ import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; -import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -423,12 +422,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /** */ @Override - public void animateCollapsePanels(int flags, boolean force) { - mCommandQueueCallbacks.animateCollapsePanels(flags, force); - } - - /** */ - @Override public void togglePanel() { mCommandQueueCallbacks.togglePanel(); } @@ -510,8 +503,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private View mReportRejectedTouch; - private boolean mExpandedVisible; - private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; private final ShadeExpansionStateManager mShadeExpansionStateManager; @@ -910,6 +901,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { updateDisplaySize(); mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId); + initShadeVisibilityListener(); + // start old BaseStatusBar.start(). mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( @@ -994,6 +987,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Lastly, call to the icon policy to install/update all the icons. mIconPolicy.init(); + // Based on teamfood flag, turn predictive back dispatch on at runtime. + if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) { + mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true); + } + mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onUnlockedChanged() { @@ -1095,6 +1093,25 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { requestTopUi, componentTag)))); } + @VisibleForTesting + void initShadeVisibilityListener() { + mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() { + @Override + public void visibilityChanged(boolean visible) { + onShadeVisibilityChanged(visible); + } + + @Override + public void expandedVisibleChanged(boolean expandedVisible) { + if (expandedVisible) { + setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); + } else { + onExpandedInvisible(); + } + } + }); + } + private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) { Trace.beginSection("CentralSurfaces#onFoldedStateChanged"); onFoldedStateChangedInternal(isFolded, willGoToSleep); @@ -1240,7 +1257,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationPanelViewController.initDependencies( this, - this::makeExpandedInvisible, + mShadeController::makeExpandedInvisible, mNotificationShelfController); BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop); @@ -1443,6 +1460,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController); mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); + mShadeController.setNotificationPresenter(mPresenter); mNotificationsController.initialize( this, mPresenter, @@ -1492,11 +1510,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return (v, event) -> { mAutoHideController.checkUserAutoHide(event); mRemoteInputManager.checkRemoteInputOutside(event); - if (event.getAction() == MotionEvent.ACTION_UP) { - if (mExpandedVisible) { - mShadeController.animateCollapsePanels(); - } - } + mShadeController.onStatusBarTouch(event); return mNotificationShadeWindowView.onTouchEvent(event); }; } @@ -1518,6 +1532,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShadeWindowViewController.setupExpandedStatusBar(); mNotificationPanelViewController = mCentralSurfacesComponent.getNotificationPanelViewController(); + mShadeController.setNotificationPanelViewController(mNotificationPanelViewController); + mShadeController.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); mCentralSurfacesComponent.getLockIconViewController().init(); mStackScrollerController = mCentralSurfacesComponent.getNotificationStackScrollLayoutController(); @@ -1840,7 +1857,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { && isLaunchForActivity) { onClosingFinished(); } else { - mShadeController.collapsePanel(true /* animate */); + mShadeController.collapseShade(true /* animate */); } } @@ -1851,7 +1868,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { onClosingFinished(); } if (launchIsFullScreen) { - instantCollapseNotificationPanel(); + mShadeController.instantCollapseShade(); } } @@ -1943,33 +1960,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public void makeExpandedVisible(boolean force) { - if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); - if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { - return; - } - - mExpandedVisible = true; - - // Expand the window to encompass the full screen in anticipation of the drag. - // This is only possible to do atomically because the status bar is at the top of the screen! - mNotificationShadeWindowController.setPanelVisible(true); - - visibilityChanged(true); - mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); - setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); - } - - @Override public void postAnimateCollapsePanels() { - mMainExecutor.execute(mShadeController::animateCollapsePanels); + mMainExecutor.execute(mShadeController::animateCollapseShade); } @Override public void postAnimateForceCollapsePanels() { - mMainExecutor.execute( - () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, - true /* force */)); + mMainExecutor.execute(mShadeController::animateCollapseShadeForced); } @Override @@ -1978,11 +1975,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public boolean isExpandedVisible() { - return mExpandedVisible; - } - - @Override public boolean isPanelExpanded() { return mPanelExpanded; } @@ -2011,46 +2003,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - void makeExpandedInvisible() { - if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible); - - if (!mExpandedVisible || mNotificationShadeWindowView == null) { - return; - } - - // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) - mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/, - 1.0f /* speedUpFactor */); - - mNotificationPanelViewController.closeQs(); - - mExpandedVisible = false; - visibilityChanged(false); - - // Update the visibility of notification shade and status bar window. - mNotificationShadeWindowController.setPanelVisible(false); - mStatusBarWindowController.setForceStatusBarVisible(false); - - // Close any guts that might be visible - mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); - - mShadeController.runPostCollapseRunnables(); + private void onExpandedInvisible() { setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) { showBouncerOrLockScreenIfKeyguard(); } else if (DEBUG) { Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen"); } - mCommandQueue.recomputeDisableFlags( - mDisplayId, - mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */); - - // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in - // the bouncer appear animation. - if (!mKeyguardStateController.isShowing()) { - WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - } } /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ @@ -2087,7 +2046,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { final boolean upOrCancel = event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL; - setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible); + setInteracting(StatusBarManager.WINDOW_STATUS_BAR, + !upOrCancel || mShadeController.isExpandedVisible()); } } @@ -2236,7 +2196,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); synchronized (mQueueLock) { pw.println("Current Status Bar state:"); - pw.println(" mExpandedVisible=" + mExpandedVisible); + pw.println(" mExpandedVisible=" + mShadeController.isExpandedVisible()); pw.println(" mDisplayMetrics=" + mDisplayMetrics); pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)); pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller) @@ -2551,10 +2511,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } if (dismissShade) { - if (mExpandedVisible && !mBouncerShowing) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed*/); + if (mShadeController.isExpandedVisible() && !mBouncerShowing) { + mShadeController.animateCollapseShadeDelayed(); } else { // Do it after DismissAction has been processed to conserve the needed // ordering. @@ -2596,7 +2554,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL; } } - mShadeController.animateCollapsePanels(flags); + mShadeController.animateCollapseShade(flags); } } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { if (mNotificationShadeWindowController != null) { @@ -2711,10 +2669,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { com.android.systemui.R.dimen.physical_power_button_center_screen_location_y)); } - // Visibility reporting protected void handleVisibleToUserChanged(boolean visibleToUser) { if (visibleToUser) { - handleVisibleToUserChangedImpl(visibleToUser); + onVisibleToUser(); mNotificationLogger.startNotificationLogging(); if (!mIsBackCallbackRegistered) { @@ -2731,7 +2688,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } else { mNotificationLogger.stopNotificationLogging(); - handleVisibleToUserChangedImpl(visibleToUser); + onInvisibleToUser(); if (mIsBackCallbackRegistered) { ViewRootImpl viewRootImpl = getViewRootImpl(); @@ -2751,41 +2708,38 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - // Visibility reporting - void handleVisibleToUserChangedImpl(boolean visibleToUser) { - if (visibleToUser) { - /* The LEDs are turned off when the notification panel is shown, even just a little bit. - * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do - * this. - */ - boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); - boolean clearNotificationEffects = - !mPresenter.isPresenterFullyCollapsed() && - (mState == StatusBarState.SHADE - || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mNotificationsController.getActiveNotificationsCount(); - if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { - notificationLoad = 1; - } - final int finalNotificationLoad = notificationLoad; - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelRevealed(clearNotificationEffects, - finalNotificationLoad); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); - } else { - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelHidden(); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); + void onVisibleToUser() { + /* The LEDs are turned off when the notification panel is shown, even just a little bit. + * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do + * this. + */ + boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); + boolean clearNotificationEffects = + !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE + || mState == StatusBarState.SHADE_LOCKED); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); + if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { + notificationLoad = 1; } + final int finalNotificationLoad = notificationLoad; + mUiBgExecutor.execute(() -> { + try { + mBarService.onPanelRevealed(clearNotificationEffects, + finalNotificationLoad); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + }); + } + void onInvisibleToUser() { + mUiBgExecutor.execute(() -> { + try { + mBarService.onPanelHidden(); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + }); } private void logStateToEventlog() { @@ -2963,7 +2917,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void updatePanelExpansionForKeyguard() { if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode() != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) { - mShadeController.instantExpandNotificationsPanel(); + mShadeController.instantExpandShade(); } } @@ -3082,7 +3036,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // too heavy for the CPU and GPU on any device. mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay); } else if (!mNotificationPanelViewController.isCollapsing()) { - instantCollapseNotificationPanel(); + mShadeController.instantCollapseShade(); } // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile @@ -3240,8 +3194,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public boolean onMenuPressed() { if (shouldUnlockOnMenuPressed()) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); + mShadeController.animateCollapseShadeForced(); return true; } return false; @@ -3286,7 +3239,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED && !isBouncerShowingOverDream()) { if (mNotificationPanelViewController.canPanelBeCollapsed()) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } return true; } @@ -3296,8 +3249,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public boolean onSpacePressed() { if (mDeviceInteractive && mState != StatusBarState.SHADE) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); + mShadeController.animateCollapseShadeForced(); return true; } return false; @@ -3336,12 +3288,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - @Override - public void instantCollapseNotificationPanel() { - mNotificationPanelViewController.instantCollapse(); - mShadeController.runPostCollapseRunnables(); - } - /** * Collapse the panel directly if we are on the main thread, post the collapsing on the main * thread if we are not. @@ -3349,9 +3295,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void collapsePanelOnMainThread() { if (Looper.getMainLooper().isCurrentThread()) { - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } else { - mContext.getMainExecutor().execute(mShadeController::collapsePanel); + mContext.getMainExecutor().execute(mShadeController::collapseShade); } } @@ -3491,7 +3437,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShadeWindowViewController.cancelCurrentTouch(); } if (mPanelExpanded && mState == StatusBarState.SHADE) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -3554,7 +3500,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // The unlocked screen off and fold to aod animations might use our LightRevealScrim - // we need to be expanded for it to be visible. if (mDozeParameters.shouldShowLightRevealScrim()) { - makeExpandedVisible(true); + mShadeController.makeExpandedVisible(true); } DejankUtils.stopDetectingBlockingIpcs(tag); @@ -3583,7 +3529,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // If we are waking up during the screen off animation, we should undo making the // expanded visible (we did that so the LightRevealScrim would be visible). if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) { - makeExpandedInvisible(); + mShadeController.makeExpandedInvisible(); } }); @@ -3638,6 +3584,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationIconAreaController.setAnimationsEnabled(!disabled); } + //TODO(b/257041702) delete + @Override + public void makeExpandedVisible(boolean force) { + mShadeController.makeExpandedVisible(force); + } + final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurningOn(Runnable onDrawn) { @@ -3918,8 +3870,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); if (BANNER_ACTION_SETUP.equals(action)) { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */); + mShadeController.animateCollapseShadeForced(); mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -3981,7 +3932,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { action.run(); }).start(); - return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard; + return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard; } @Override @@ -4076,8 +4027,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mMainExecutor.execute(runnable); } - @Override - public void visibilityChanged(boolean visible) { + private void onShadeVisibilityChanged(boolean visible) { if (mVisible != visible) { mVisible = visible; if (!visible) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java index 26e6db664e07..4beb87ddae2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java @@ -15,23 +15,21 @@ package com.android.systemui.statusbar.phone; import android.app.StatusBarManager; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -43,9 +41,9 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { private final List<Callback> mCallbacks = new ArrayList<>(); private final Context mContext; + private final Executor mMainExecutor; private final UserManager mUserManager; private final UserTracker mUserTracker; - private final BroadcastDispatcher mBroadcastDispatcher; private final LinkedList<UserInfo> mProfiles; private boolean mListening; private int mCurrentUser; @@ -53,12 +51,12 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { /** */ @Inject - public ManagedProfileControllerImpl(Context context, UserTracker userTracker, - BroadcastDispatcher broadcastDispatcher) { + public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor, + UserTracker userTracker) { mContext = context; + mMainExecutor = mainExecutor; mUserManager = UserManager.get(mContext); mUserTracker = userTracker; - mBroadcastDispatcher = broadcastDispatcher; mProfiles = new LinkedList<UserInfo>(); } @@ -130,30 +128,34 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { } private void setListening(boolean listening) { + if (mListening == listening) { + return; + } mListening = listening; if (listening) { reloadManagedProfiles(); - - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - mBroadcastDispatcher.registerReceiver( - mReceiver, filter, null /* handler */, UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); } else { - mBroadcastDispatcher.unregisterReceiver(mReceiver); + mUserTracker.removeCallback(mUserChangedCallback); } } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - reloadManagedProfiles(); - for (Callback callback : mCallbacks) { - callback.onManagedProfileChanged(); - } - } - }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + reloadManagedProfiles(); + for (Callback callback : mCallbacks) { + callback.onManagedProfileChanged(); + } + } + + @Override + public void onProfilesChanged(@NonNull List<UserInfo> profiles) { + reloadManagedProfiles(); + for (Callback callback : mCallbacks) { + callback.onManagedProfileChanged(); + } + } + }; } 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 dcbabaac8a5b..3b160c85ca15 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -469,6 +469,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // Don't expand to the bouncer. Instead transition back to the lock screen (see // CentralSurfaces#showBouncerOrLockScreenIfKeyguard) return; + } else if (mKeyguardStateController.isOccluded() + && !mDreamOverlayStateController.isOverlayActive()) { + return; } else if (needsFullscreenBouncer()) { if (mPrimaryBouncer != null) { mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index b6ae4a088880..05bf8604c2c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -260,11 +260,11 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte if (showOverLockscreen) { mShadeController.addPostCollapseAction(runnable); - mShadeController.collapsePanel(true /* animate */); + mShadeController.collapseShade(true /* animate */); } else if (mKeyguardStateController.isShowing() && mCentralSurfaces.isOccluded()) { mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } else { runnable.run(); } @@ -406,7 +406,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte private void expandBubbleStack(NotificationEntry entry) { mBubblesManagerOptional.get().expandStackAndSelectBubble(entry); - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } private void startNotificationIntent( @@ -593,9 +593,9 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte private void collapseOnMainThread() { if (Looper.getMainLooper().isCurrentThread()) { - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } else { - mMainThreadHandler.post(mShadeController::collapsePanel); + mMainThreadHandler.post(mShadeController::collapseShade); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index 8a49850b1822..7fe01825890f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -180,7 +180,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, } }; mShadeController.postOnShadeExpanded(clickPendingViewRunnable); - mShadeController.instantExpandNotificationsPanel(); + mShadeController.instantExpandShade(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt index 7aa5ee1389f3..8ff9198da119 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt @@ -23,9 +23,10 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.qs.SettingObserver -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange +import com.android.systemui.statusbar.pipeline.dagger.AirplaneTableLog import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -58,7 +59,7 @@ class AirplaneModeRepositoryImpl constructor( @Background private val bgHandler: Handler, private val globalSettings: GlobalSettings, - logger: ConnectivityPipelineLogger, + @AirplaneTableLog logger: TableLogBuffer, @Application scope: CoroutineScope, ) : AirplaneModeRepository { // TODO(b/254848912): Replace this with a generic SettingObserver coroutine once we have it. @@ -82,7 +83,12 @@ constructor( awaitClose { observer.isListening = false } } .distinctUntilChanged() - .logInputChange(logger, "isAirplaneMode") + .logDiffsForTable( + logger, + columnPrefix = "", + columnName = "isAirplaneMode", + initialValue = false + ) .stateIn( scope, started = SharingStarted.WhileSubscribed(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt new file mode 100644 index 000000000000..4f70f660187f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.dagger + +import javax.inject.Qualifier + +/** Airplane mode logs in table format. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class AirplaneTableLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 0662fb3d52b9..c961422086f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -71,5 +71,13 @@ abstract class StatusBarPipelineModule { fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { return factory.create("WifiTableLog", 100) } + + @JvmStatic + @Provides + @SysUISingleton + @AirplaneTableLog + fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("AirplaneTableLog", 30) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt index 8436b13d7038..a682a5711a6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt @@ -31,15 +31,19 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { if (prevVal is Inactive) { return } - row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) if (prevVal is CarrierMerged) { // The only difference between CarrierMerged and Inactive is the type + row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) return } // When changing from Active to Inactive, we need to log diffs to all the fields. - logDiffsFromActiveToNotActive(prevVal as Active, row) + logFullNonActiveNetwork(TYPE_INACTIVE, row) + } + + override fun logFull(row: TableRowLogger) { + logFullNonActiveNetwork(TYPE_INACTIVE, row) } } @@ -56,15 +60,15 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { if (prevVal is CarrierMerged) { return } - row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) if (prevVal is Inactive) { // The only difference between CarrierMerged and Inactive is the type. + row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) return } // When changing from Active to CarrierMerged, we need to log diffs to all the fields. - logDiffsFromActiveToNotActive(prevVal as Active, row) + logFullNonActiveNetwork(TYPE_CARRIER_MERGED, row) } } @@ -121,7 +125,11 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_VALIDATED, isValidated) } if (prevVal !is Active || prevVal.level != level) { - row.logChange(COL_LEVEL, level ?: LEVEL_DEFAULT) + if (level != null) { + row.logChange(COL_LEVEL, level) + } else { + row.logChange(COL_LEVEL, LEVEL_DEFAULT) + } } if (prevVal !is Active || prevVal.ssid != ssid) { row.logChange(COL_SSID, ssid) @@ -143,7 +151,6 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } } - override fun toString(): String { // Only include the passpoint-related values in the string if we have them. (Most // networks won't have them so they'll be mostly clutter.) @@ -170,21 +177,15 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } } - internal fun logDiffsFromActiveToNotActive(prevActive: Active, row: TableRowLogger) { + internal fun logFullNonActiveNetwork(type: String, row: TableRowLogger) { + row.logChange(COL_NETWORK_TYPE, type) row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) row.logChange(COL_VALIDATED, false) row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_SSID, null) - - if (prevActive.isPasspointAccessPoint) { - row.logChange(COL_PASSPOINT_ACCESS_POINT, false) - } - if (prevActive.isOnlineSignUpForPasspointAccessPoint) { - row.logChange(COL_ONLINE_SIGN_UP, false) - } - if (prevActive.passpointProviderFriendlyName != null) { - row.logChange(COL_PASSPOINT_NAME, null) - } + row.logChange(COL_PASSPOINT_ACCESS_POINT, false) + row.logChange(COL_ONLINE_SIGN_UP, false) + row.logChange(COL_PASSPOINT_NAME, null) } } @@ -201,5 +202,5 @@ const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" -const val LEVEL_DEFAULT = -1 -const val NETWORK_ID_DEFAULT = -1 +val LEVEL_DEFAULT: String? = null +val NETWORK_ID_DEFAULT: String? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index d84cbcc60853..6875b523a962 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -120,6 +120,7 @@ public class Clock extends TextView implements @Override public void onUserChanged(int newUser, @NonNull Context userContext) { mCurrentUserId = newUser; + updateClock(); } }; @@ -190,7 +191,6 @@ public class Clock extends TextView implements filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); // NOTE: This receiver could run before this method returns, as it's not dispatching // on the main thread and BroadcastDispatcher may not need to register with Context. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java index b234e9c4e746..63b9ff9717d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java @@ -28,11 +28,14 @@ import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.settings.UserTracker; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Date; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -45,22 +48,34 @@ public class NextAlarmControllerImpl extends BroadcastReceiver private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>(); + private final UserTracker mUserTracker; private AlarmManager mAlarmManager; private AlarmManager.AlarmClockInfo mNextAlarm; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + updateNextAlarm(); + } + }; + /** */ @Inject public NextAlarmControllerImpl( + @Main Executor mainExecutor, AlarmManager alarmManager, BroadcastDispatcher broadcastDispatcher, - DumpManager dumpManager) { + DumpManager dumpManager, + UserTracker userTracker) { dumpManager.registerDumpable("NextAlarmController", this); mAlarmManager = alarmManager; + mUserTracker = userTracker; IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mainExecutor); updateNextAlarm(); } @@ -98,14 +113,13 @@ public class NextAlarmControllerImpl extends BroadcastReceiver public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (action.equals(Intent.ACTION_USER_SWITCHED) - || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { + if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { updateNextAlarm(); } } private void updateNextAlarm() { - mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT); + mNextAlarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId()); fireNextAlarmChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt index f71d596ff835..b61b2e66fb22 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt @@ -20,7 +20,6 @@ import java.util.concurrent.atomic.AtomicReference import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onStart @@ -58,6 +57,22 @@ fun <T, R> Flow<T>.pairwiseBy( onStart { emit(initialValue) }.pairwiseBy(transform) /** + * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform]. + * + * + * The output of [getInitialValue] will be used as the "old" value for the first emission. As + * opposed to the initial value in the above [pairwiseBy], [getInitialValue] can do some work before + * returning the initial value. + * + * Useful for code that needs to compare the current value to the previous value. + */ +fun <T, R> Flow<T>.pairwiseBy( + getInitialValue: suspend () -> T, + transform: suspend (previousValue: T, newValue: T) -> R, +): Flow<R> = + onStart { emit(getInitialValue()) }.pairwiseBy(transform) + +/** * Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new * Flow will not start emitting until it has received two emissions from the upstream Flow. * diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index a4384d5810ce..7033ccde8c7d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -549,7 +549,7 @@ public class BubblesManager { } catch (RemoteException e) { Log.e(TAG, e.getMessage()); } - mShadeController.collapsePanel(true); + mShadeController.collapseShade(true); if (entry.getRow() != null) { entry.getRow().updateBubbleButton(); } @@ -597,7 +597,7 @@ public class BubblesManager { } if (shouldBubble) { - mShadeController.collapsePanel(true); + mShadeController.collapseShade(true); if (entry.getRow() != null) { entry.getRow().updateBubbleButton(); } diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties new file mode 100644 index 000000000000..2a75bd98bfe8 --- /dev/null +++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties @@ -0,0 +1,16 @@ +# +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +sdk=NEWEST_SDK
\ No newline at end of file diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java new file mode 100644 index 000000000000..188dff21efa4 --- /dev/null +++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.robotests; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; +import static com.google.common.truth.Truth.assertThat; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SysuiResourceLoadingTest extends SysuiRoboBase { + @Test + public void testResources() { + assertThat(getContext().getString(com.android.systemui.R.string.app_label)) + .isEqualTo("System UI"); + assertThat(getContext().getString(com.android.systemui.tests.R.string.test_content)) + .isNotEmpty(); + } +} diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java new file mode 100644 index 000000000000..d9686bbeb0cd --- /dev/null +++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.robotests; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; + +public class SysuiRoboBase { + public Context getContext() { + return InstrumentationRegistry.getContext(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 181839ab512f..0627fc6c542f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -77,7 +77,6 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.decor.CornerDecorProvider; import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.CutoutDecorProviderImpl; @@ -132,8 +131,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { @Mock private TunerService mTunerService; @Mock - private BroadcastDispatcher mBroadcastDispatcher; - @Mock private UserTracker mUserTracker; @Mock private PrivacyDotViewController mDotViewController; @@ -223,8 +220,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { mExecutor)); mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, - mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController, - mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) { + mTunerService, mUserTracker, mDotViewController, mThreadFactory, + mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) { @Override public void start() { super.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java new file mode 100644 index 000000000000..60a02582269c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.res.Configuration; +import android.graphics.Color; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class UdfpsEnrollViewTest extends SysuiTestCase { + + private static String ENROLL_PROGRESS_COLOR_LIGHT = "#699FF3"; + private static String ENROLL_PROGRESS_COLOR_DARK = "#7DA7F1"; + + @Test + public void fingerprintUdfpsEnroll_usesCorrectThemeCheckmarkFillColor() { + final Configuration config = mContext.getResources().getConfiguration(); + final boolean isDarkThemeOn = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES; + final int currentColor = mContext.getColor(R.color.udfps_enroll_progress); + + assertThat(currentColor).isEqualTo(Color.parseColor(isDarkThemeOn + ? ENROLL_PROGRESS_COLOR_DARK : ENROLL_PROGRESS_COLOR_LIGHT)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index c31fd828c730..1b34706bd220 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.controls.controller import android.app.PendingIntent -import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.ContextWrapper @@ -31,7 +30,6 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.backup.BackupHelper -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController @@ -85,10 +83,8 @@ class ControlsControllerImplTest : SysuiTestCase() { @Mock private lateinit var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var listingController: ControlsListingController - @Mock(stubOnly = true) + @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var userFileManager: UserFileManager @@ -104,7 +100,7 @@ class ControlsControllerImplTest : SysuiTestCase() { ArgumentCaptor<ControlsBindingController.LoadCallback> @Captor - private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> + private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback> @Captor private lateinit var listingCallbackCaptor: ArgumentCaptor<ControlsListingController.ControlsListingCallback> @@ -170,16 +166,15 @@ class ControlsControllerImplTest : SysuiTestCase() { uiController, bindingController, listingController, - broadcastDispatcher, userFileManager, + userTracker, Optional.of(persistenceWrapper), - mock(DumpManager::class.java), - userTracker + mock(DumpManager::class.java) ) controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper - verify(broadcastDispatcher).registerReceiver( - capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL), anyInt(), any() + verify(userTracker).addCallback( + capture(userTrackerCallbackCaptor), any() ) verify(listingController).addCallback(capture(listingCallbackCaptor)) @@ -227,11 +222,10 @@ class ControlsControllerImplTest : SysuiTestCase() { uiController, bindingController, listingController, - broadcastDispatcher, userFileManager, + userTracker, Optional.of(persistenceWrapper), - mock(DumpManager::class.java), - userTracker + mock(DumpManager::class.java) ) assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites()) } @@ -518,14 +512,8 @@ class ControlsControllerImplTest : SysuiTestCase() { delayableExecutor.runAllReady() reset(persistenceWrapper) - val intent = Intent(Intent.ACTION_USER_SWITCHED).apply { - putExtra(Intent.EXTRA_USER_HANDLE, otherUser) - } - val pendingResult = mock(BroadcastReceiver.PendingResult::class.java) - `when`(pendingResult.sendingUserId).thenReturn(otherUser) - broadcastReceiverCaptor.value.pendingResult = pendingResult - broadcastReceiverCaptor.value.onReceive(mContext, intent) + userTrackerCallbackCaptor.value.onUserChanged(otherUser, mContext) verify(persistenceWrapper).changeFileAndBackupManager(any(), any()) verify(persistenceWrapper).readFavorites() diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index 98ff8d1d8845..c677f19f93e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -31,6 +31,7 @@ import android.service.controls.ControlsProviderService import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.settingslib.applications.ServiceListing +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.dump.DumpManager @@ -110,6 +111,12 @@ class ControlsListingControllerImplTest : SysuiTestCase() { .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED) mContext.setMockPackageManager(packageManager) + mContext.orCreateTestableResources + .addOverride( + R.array.config_controlsPreferredPackages, + arrayOf(componentName.packageName) + ) + // Return true by default, we'll test the false path `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true) @@ -482,6 +489,35 @@ class ControlsListingControllerImplTest : SysuiTestCase() { } @Test + fun testPackageNotPreferred_nullPanel() { + mContext.orCreateTestableResources + .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>()) + + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + `when`(packageManager.getComponentEnabledSetting(eq(activityName))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + @Test fun testListingsNotModifiedByCallback() { // This test checks that if the list passed to the callback is modified, it has no effect // in the resulting services diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt index cedde58746d2..32c5b3f99d41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt @@ -36,8 +36,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceIn import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock @@ -89,6 +89,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt index 623becf166d3..7205f3068abb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt @@ -37,25 +37,29 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { @Mock private lateinit var cameraGestureHelper: CameraGestureHelper @Mock private lateinit var context: Context + private lateinit var underTest: CameraQuickAffordanceConfig @Before fun setUp() { MockitoAnnotations.initMocks(this) - underTest = CameraQuickAffordanceConfig( + + underTest = + CameraQuickAffordanceConfig( context, - cameraGestureHelper, - ) + ) { + cameraGestureHelper + } } @Test fun `affordance triggered -- camera launch called`() { - //when + // When val result = underTest.onTriggered(null) - //then + // Then verify(cameraGestureHelper) - .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) + .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt new file mode 100644 index 000000000000..cda701819d60 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.content.Context +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.statusbar.policy.FlashlightController +import com.android.systemui.utils.leaks.FakeFlashlightController +import com.android.systemui.utils.leaks.LeakCheckedTest +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() { + + @Mock private lateinit var context: Context + private lateinit var flashlightController: FakeFlashlightController + private lateinit var underTest : FlashlightQuickAffordanceConfig + + @Before + fun setUp() { + injectLeakCheckedDependency(FlashlightController::class.java) + MockitoAnnotations.initMocks(this) + + flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController + underTest = FlashlightQuickAffordanceConfig(context, flashlightController) + } + + @Test + fun `flashlight is off -- triggered -- icon is on and active`() = runTest { + //given + flashlightController.isEnabled = false + flashlightController.isAvailable = true + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + underTest.onTriggered(null) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertEquals(R.drawable.ic_flashlight_on, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest { + //given + flashlightController.isEnabled = true + flashlightController.isAvailable = true + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + underTest.onTriggered(null) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertEquals(R.drawable.ic_flashlight_off, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest { + //given + flashlightController.isEnabled = true + flashlightController.isAvailable = false + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + flashlightController.onFlashlightError() + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertEquals(R.drawable.ic_flashlight_off, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight availability now off -- hidden`() = runTest { + //given + flashlightController.isEnabled = true + flashlightController.isAvailable = false + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + flashlightController.onFlashlightAvailabilityChanged(false) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + job.cancel() + } + + @Test + fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest { + //given + flashlightController.isEnabled = true + flashlightController.isAvailable = false + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + flashlightController.onFlashlightAvailabilityChanged(true) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active) + assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest { + //given + flashlightController.isEnabled = false + flashlightController.isAvailable = false + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + flashlightController.onFlashlightAvailabilityChanged(true) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive) + assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight available -- picker state default`() = runTest { + //given + flashlightController.isAvailable = true + + //when + val result = underTest.getPickerScreenState() + + //then + assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default) + } + + @Test + fun `flashlight not available -- picker state unavailable`() = runTest { + //given + flashlightController.isAvailable = false + + //when + val result = underTest.getPickerScreenState() + + //then + assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 8ef921eaa50a..552b8cb96525 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -89,6 +89,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = FakeUserTracker(), + broadcastDispatcher = fakeBroadcastDispatcher, ) settings = FakeSettings() settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt index d8ee9f113d33..6a2376b5bc4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.quickaffordance +import android.content.Intent import android.content.SharedPreferences import android.content.pm.UserInfo import androidx.test.filters.SmallTest @@ -27,10 +28,15 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -38,8 +44,12 @@ import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { @@ -60,15 +70,23 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { sharedPrefs.getOrPut(userId) { FakeSharedPreferences() } } userTracker = FakeUserTracker() + val dispatcher = UnconfinedTestDispatcher() + Dispatchers.setMain(dispatcher) underTest = KeyguardQuickAffordanceSelectionManager( context = context, userFileManager = userFileManager, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) } + @After + fun tearDown() { + Dispatchers.resetMain() + } + @Test fun setSelections() = runTest { overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>()) @@ -318,6 +336,22 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { job.cancel() } + @Test + fun `responds to backup and restore by reloading the selections from disk`() = runTest { + overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>()) + val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.selections.toList(affordanceIdsBySlotId) + } + clearInvocations(userFileManager) + + fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent()) + + verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt()) + job.cancel() + } + private fun assertSelections( observed: Map<String, List<String>>?, expected: Map<String, List<String>>, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 5c75417c3473..652fae968744 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -76,6 +76,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = FakeUserTracker(), + broadcastDispatcher = fakeBroadcastDispatcher, ) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index c2650ec455d8..ba7c40b6b381 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -252,6 +252,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index b79030602368..8d0c4ef4b3da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -113,6 +113,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 8b166bd89426..32849cdce02e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -136,6 +136,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt index 688c66ac80c9..2c8d7abd4f4a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt @@ -46,6 +46,109 @@ class TableLogBufferTest : SysuiTestCase() { } @Test + fun dumpChanges_hasHeader() { + val dumpedString = dumpChanges() + + assertThat(logLines(dumpedString)[0]).isEqualTo(HEADER_PREFIX + NAME) + } + + @Test + fun dumpChanges_hasVersion() { + val dumpedString = dumpChanges() + + assertThat(logLines(dumpedString)[1]).isEqualTo("version $VERSION") + } + + @Test + fun dumpChanges_hasFooter() { + val dumpedString = dumpChanges() + + assertThat(logLines(dumpedString).last()).isEqualTo(FOOTER_PREFIX + NAME) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_str_separatorNotAllowedInPrefix() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("columnName", "stringValue") + } + } + underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_bool_separatorNotAllowedInPrefix() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("columnName", true) + } + } + underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_int_separatorNotAllowedInPrefix() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("columnName", 567) + } + } + underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_str_separatorNotAllowedInColumnName() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("column${SEPARATOR}Name", "stringValue") + } + } + underTest.logDiffs("prefix", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_bool_separatorNotAllowedInColumnName() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("column${SEPARATOR}Name", true) + } + } + underTest.logDiffs("prefix", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_int_separatorNotAllowedInColumnName() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("column${SEPARATOR}Name", 456) + } + } + underTest.logDiffs("prefix", TestDiffable(), next) + } + + @Test + fun logChange_bool_dumpsCorrectly() { + systemClock.setCurrentTimeMillis(4000L) + + underTest.logChange("prefix", "columnName", true) + + val dumpedString = dumpChanges() + val expected = + TABLE_LOG_DATE_FORMAT.format(4000L) + + SEPARATOR + + "prefix.columnName" + + SEPARATOR + + "true" + assertThat(dumpedString).contains(expected) + } + + @Test fun dumpChanges_strChange_logsFromNext() { systemClock.setCurrentTimeMillis(100L) @@ -66,11 +169,14 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("prefix") - assertThat(dumpedString).contains("stringValChange") - assertThat(dumpedString).contains("newStringVal") + val expected = + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + "prefix.stringValChange" + + SEPARATOR + + "newStringVal" + assertThat(dumpedString).contains(expected) assertThat(dumpedString).doesNotContain("prevStringVal") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) } @Test @@ -94,11 +200,14 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("prefix") - assertThat(dumpedString).contains("booleanValChange") - assertThat(dumpedString).contains("true") + val expected = + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + "prefix.booleanValChange" + + SEPARATOR + + "true" + assertThat(dumpedString).contains(expected) assertThat(dumpedString).doesNotContain("false") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) } @Test @@ -122,11 +231,14 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("prefix") - assertThat(dumpedString).contains("intValChange") - assertThat(dumpedString).contains("67890") + val expected = + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + "prefix.intValChange" + + SEPARATOR + + "67890" + assertThat(dumpedString).contains(expected) assertThat(dumpedString).doesNotContain("12345") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) } @Test @@ -152,9 +264,9 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() // THEN the dump still works - assertThat(dumpedString).contains("booleanValChange") - assertThat(dumpedString).contains("true") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) + val expected = + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + "booleanValChange" + SEPARATOR + "true" + assertThat(dumpedString).contains(expected) } @Test @@ -186,15 +298,34 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("valChange") - assertThat(dumpedString).contains("stateValue12") - assertThat(dumpedString).contains("stateValue20") - assertThat(dumpedString).contains("stateValue40") - assertThat(dumpedString).contains("stateValue45") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(12000L)) - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(20000L)) - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(40000L)) - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(45000L)) + val expected1 = + TABLE_LOG_DATE_FORMAT.format(12000L) + + SEPARATOR + + "valChange" + + SEPARATOR + + "stateValue12" + val expected2 = + TABLE_LOG_DATE_FORMAT.format(20000L) + + SEPARATOR + + "valChange" + + SEPARATOR + + "stateValue20" + val expected3 = + TABLE_LOG_DATE_FORMAT.format(40000L) + + SEPARATOR + + "valChange" + + SEPARATOR + + "stateValue40" + val expected4 = + TABLE_LOG_DATE_FORMAT.format(45000L) + + SEPARATOR + + "valChange" + + SEPARATOR + + "stateValue45" + assertThat(dumpedString).contains(expected1) + assertThat(dumpedString).contains(expected2) + assertThat(dumpedString).contains(expected3) + assertThat(dumpedString).contains(expected4) } @Test @@ -214,10 +345,73 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("status") - assertThat(dumpedString).contains("in progress") - assertThat(dumpedString).contains("connected") - assertThat(dumpedString).contains("false") + val timestamp = TABLE_LOG_DATE_FORMAT.format(100L) + val expected1 = timestamp + SEPARATOR + "status" + SEPARATOR + "in progress" + val expected2 = timestamp + SEPARATOR + "connected" + SEPARATOR + "false" + assertThat(dumpedString).contains(expected1) + assertThat(dumpedString).contains(expected2) + } + + @Test + fun logChange_rowInitializer_dumpsCorrectly() { + systemClock.setCurrentTimeMillis(100L) + + underTest.logChange("") { row -> + row.logChange("column1", "val1") + row.logChange("column2", 2) + row.logChange("column3", true) + } + + val dumpedString = dumpChanges() + + val timestamp = TABLE_LOG_DATE_FORMAT.format(100L) + val expected1 = timestamp + SEPARATOR + "column1" + SEPARATOR + "val1" + val expected2 = timestamp + SEPARATOR + "column2" + SEPARATOR + "2" + val expected3 = timestamp + SEPARATOR + "column3" + SEPARATOR + "true" + assertThat(dumpedString).contains(expected1) + assertThat(dumpedString).contains(expected2) + assertThat(dumpedString).contains(expected3) + } + + @Test + fun logChangeAndLogDiffs_bothLogged() { + systemClock.setCurrentTimeMillis(100L) + + underTest.logChange("") { row -> + row.logChange("column1", "val1") + row.logChange("column2", 2) + row.logChange("column3", true) + } + + systemClock.setCurrentTimeMillis(200L) + val prevDiffable = object : TestDiffable() {} + val nextDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("column1", "newVal1") + row.logChange("column2", 222) + row.logChange("column3", false) + } + } + + underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable) + + val dumpedString = dumpChanges() + + val timestamp1 = TABLE_LOG_DATE_FORMAT.format(100L) + val expected1 = timestamp1 + SEPARATOR + "column1" + SEPARATOR + "val1" + val expected2 = timestamp1 + SEPARATOR + "column2" + SEPARATOR + "2" + val expected3 = timestamp1 + SEPARATOR + "column3" + SEPARATOR + "true" + val timestamp2 = TABLE_LOG_DATE_FORMAT.format(200L) + val expected4 = timestamp2 + SEPARATOR + "column1" + SEPARATOR + "newVal1" + val expected5 = timestamp2 + SEPARATOR + "column2" + SEPARATOR + "222" + val expected6 = timestamp2 + SEPARATOR + "column3" + SEPARATOR + "false" + assertThat(dumpedString).contains(expected1) + assertThat(dumpedString).contains(expected2) + assertThat(dumpedString).contains(expected3) + assertThat(dumpedString).contains(expected4) + assertThat(dumpedString).contains(expected5) + assertThat(dumpedString).contains(expected6) } @Test @@ -247,14 +441,24 @@ class TableLogBufferTest : SysuiTestCase() { } private fun dumpChanges(): String { - underTest.dumpChanges(PrintWriter(outputWriter)) + underTest.dump(PrintWriter(outputWriter), arrayOf()) return outputWriter.toString() } - private abstract class TestDiffable : Diffable<TestDiffable> { + private fun logLines(string: String): List<String> { + return string.split("\n").filter { it.isNotBlank() } + } + + private open class TestDiffable : Diffable<TestDiffable> { override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {} } } private const val NAME = "TestTableBuffer" private const val MAX_SIZE = 10 + +// Copying these here from [TableLogBuffer] so that we catch any accidental versioning change +private const val HEADER_PREFIX = "SystemUI StateChangeTableSection START: " +private const val FOOTER_PREFIX = "SystemUI StateChangeTableSection END: " +private const val SEPARATOR = "|" // TBD +private const val VERSION = "1" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt index 84fdfd78e9fc..136ace173795 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT +import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -79,6 +80,7 @@ private fun <T> any(): T = Mockito.any<T>() class MediaResumeListenerTest : SysuiTestCase() { @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var device: MediaDeviceData @Mock private lateinit var token: MediaSession.Token @@ -131,12 +133,15 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor) whenever(mockContext.packageManager).thenReturn(context.packageManager) whenever(mockContext.contentResolver).thenReturn(context.contentResolver) + whenever(mockContext.userId).thenReturn(context.userId) executor = FakeExecutor(clock) resumeListener = MediaResumeListener( mockContext, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, @@ -177,6 +182,8 @@ class MediaResumeListenerTest : SysuiTestCase() { MediaResumeListener( context, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, @@ -185,7 +192,7 @@ class MediaResumeListenerTest : SysuiTestCase() { ) listener.setManager(mediaDataManager) verify(broadcastDispatcher, never()) - .registerReceiver(eq(listener.userChangeReceiver), any(), any(), any(), anyInt(), any()) + .registerReceiver(eq(listener.userUnlockReceiver), any(), any(), any(), anyInt(), any()) // When data is loaded, we do NOT execute or update anything listener.onMediaDataLoaded(KEY, OLD_KEY, data) @@ -289,7 +296,7 @@ class MediaResumeListenerTest : SysuiTestCase() { resumeListener.setManager(mediaDataManager) verify(broadcastDispatcher) .registerReceiver( - eq(resumeListener.userChangeReceiver), + eq(resumeListener.userUnlockReceiver), any(), any(), any(), @@ -299,7 +306,8 @@ class MediaResumeListenerTest : SysuiTestCase() { // When we get an unlock event val intent = Intent(Intent.ACTION_USER_UNLOCKED) - resumeListener.userChangeReceiver.onReceive(context, intent) + intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + resumeListener.userUnlockReceiver.onReceive(context, intent) // Then we should attempt to find recent media for each saved component verify(resumeBrowser, times(3)).findRecentMedia() @@ -375,6 +383,8 @@ class MediaResumeListenerTest : SysuiTestCase() { MediaResumeListener( mockContext, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, @@ -386,7 +396,8 @@ class MediaResumeListenerTest : SysuiTestCase() { // When we load a component that was played recently val intent = Intent(Intent.ACTION_USER_UNLOCKED) - resumeListener.userChangeReceiver.onReceive(mockContext, intent) + intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + resumeListener.userUnlockReceiver.onReceive(mockContext, intent) // We add its resume controls verify(resumeBrowser, times(1)).findRecentMedia() @@ -404,6 +415,8 @@ class MediaResumeListenerTest : SysuiTestCase() { MediaResumeListener( mockContext, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, @@ -415,7 +428,8 @@ class MediaResumeListenerTest : SysuiTestCase() { // When we load a component that is not recent val intent = Intent(Intent.ACTION_USER_UNLOCKED) - resumeListener.userChangeReceiver.onReceive(mockContext, intent) + intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + resumeListener.userUnlockReceiver.onReceive(mockContext, intent) // We do not try to add resume controls verify(resumeBrowser, times(0)).findRecentMedia() @@ -443,6 +457,8 @@ class MediaResumeListenerTest : SysuiTestCase() { MediaResumeListener( mockContext, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index f43a34f6e89b..80adbf025e0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -44,14 +44,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.IntentFilter; import android.content.res.Resources; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.SystemClock; -import android.os.UserHandle; import android.provider.DeviceConfig; import android.telecom.TelecomManager; import android.testing.AndroidTestingRunner; @@ -79,7 +76,6 @@ import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; @@ -119,6 +115,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Optional; +import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -166,7 +163,7 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private Handler mHandler; @Mock - private BroadcastDispatcher mBroadcastDispatcher; + private UserTracker mUserTracker; @Mock private UiEventLogger mUiEventLogger; @Mock @@ -315,14 +312,10 @@ public class NavigationBarTest extends SysuiTestCase { } @Test - public void testRegisteredWithDispatcher() { + public void testRegisteredWithUserTracker() { mNavigationBar.init(); mNavigationBar.onViewAttached(); - verify(mBroadcastDispatcher).registerReceiverWithHandler( - any(BroadcastReceiver.class), - any(IntentFilter.class), - any(Handler.class), - any(UserHandle.class)); + verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class)); } @Test @@ -463,7 +456,7 @@ public class NavigationBarTest extends SysuiTestCase { mStatusBarStateController, mStatusBarKeyguardViewManager, mMockSysUiState, - mBroadcastDispatcher, + mUserTracker, mCommandQueue, Optional.of(mock(Pip.class)), Optional.of(mock(Recents.class)), diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index c377c374148f..338182a3e304 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -48,6 +48,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.power.PowerUI.WarningsUI; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -85,6 +86,7 @@ public class PowerUITest extends SysuiTestCase { private PowerUI mPowerUI; @Mock private EnhancedEstimates mEnhancedEstimates; @Mock private PowerManager mPowerManager; + @Mock private UserTracker mUserTracker; @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private IThermalService mThermalServiceMock; private IThermalEventListener mUsbThermalEventListener; @@ -682,7 +684,8 @@ public class PowerUITest extends SysuiTestCase { private void createPowerUi() { mPowerUI = new PowerUI( mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy, - mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager); + mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager, + mUserTracker); mPowerUI.mThermalService = mThermalServiceMock; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 013e58ed99d7..69f3e987ec1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -33,6 +33,9 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -49,12 +52,16 @@ import org.mockito.MockitoAnnotations; */ public class RecordingControllerTest extends SysuiTestCase { + private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); + private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @Mock private RecordingController.RecordingStateChangeCallback mCallback; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private UserContextProvider mUserContextProvider; + @Mock + private UserTracker mUserTracker; private RecordingController mController; @@ -63,7 +70,8 @@ public class RecordingControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mController = new RecordingController(mBroadcastDispatcher, mUserContextProvider); + mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, + mUserContextProvider, mUserTracker); mController.addCallback(mCallback); } @@ -176,9 +184,7 @@ public class RecordingControllerTest extends SysuiTestCase { mController.updateState(true); // and user is changed - Intent intent = new Intent(Intent.ACTION_USER_SWITCHED) - .putExtra(Intent.EXTRA_USER_HANDLE, USER_ID); - mController.mUserChangeReceiver.onReceive(mContext, intent); + mController.mUserChangedCallback.onUserChanged(USER_ID, mContext); // Ensure that the recording was stopped verify(mCallback).onRecordingEnd(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt index 6d9b01e28aa4..020a86611552 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt @@ -50,24 +50,20 @@ class UserFileManagerImplTest : SysuiTestCase() { lateinit var userFileManager: UserFileManagerImpl lateinit var backgroundExecutor: FakeExecutor - @Mock - lateinit var userManager: UserManager - @Mock - lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock lateinit var userManager: UserManager + @Mock lateinit var broadcastDispatcher: BroadcastDispatcher @Before fun setUp() { MockitoAnnotations.initMocks(this) backgroundExecutor = FakeExecutor(FakeSystemClock()) - userFileManager = UserFileManagerImpl(context, userManager, - broadcastDispatcher, backgroundExecutor) + userFileManager = + UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor) } @After fun end() { - val dir = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID) + val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID) dir.deleteRecursively() } @@ -82,13 +78,14 @@ class UserFileManagerImplTest : SysuiTestCase() { @Test fun testGetSharedPreferences() { val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11) - val secondaryUserDir = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - UserFileManagerImpl.SHARED_PREFS, - TEST_FILE_NAME - ) + val secondaryUserDir = + Environment.buildPath( + context.filesDir, + UserFileManagerImpl.ID, + "11", + UserFileManagerImpl.SHARED_PREFS, + TEST_FILE_NAME + ) assertThat(secondarySharedPref).isNotNull() assertThat(secondaryUserDir.exists()) @@ -101,32 +98,35 @@ class UserFileManagerImplTest : SysuiTestCase() { val userFileManager = spy(userFileManager) userFileManager.start() verify(userFileManager).clearDeletedUserData() - verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java), - any(IntentFilter::class.java), - any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull()) + verify(broadcastDispatcher) + .registerReceiver( + any(BroadcastReceiver::class.java), + any(IntentFilter::class.java), + any(Executor::class.java), + isNull(), + eq(Context.RECEIVER_EXPORTED), + isNull() + ) } @Test fun testClearDeletedUserData() { - val dir = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - "files" - ) + val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files") dir.mkdirs() - val file = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - "files", - TEST_FILE_NAME - ) - val secondaryUserDir = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - ) + val file = + Environment.buildPath( + context.filesDir, + UserFileManagerImpl.ID, + "11", + "files", + TEST_FILE_NAME + ) + val secondaryUserDir = + Environment.buildPath( + context.filesDir, + UserFileManagerImpl.ID, + "11", + ) file.createNewFile() assertThat(secondaryUserDir.exists()).isTrue() assertThat(file.exists()).isTrue() @@ -139,15 +139,16 @@ class UserFileManagerImplTest : SysuiTestCase() { @Test fun testEnsureParentDirExists() { - val file = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - "files", - TEST_FILE_NAME - ) + val file = + Environment.buildPath( + context.filesDir, + UserFileManagerImpl.ID, + "11", + "files", + TEST_FILE_NAME + ) assertThat(file.parentFile.exists()).isFalse() - userFileManager.ensureParentDirExists(file) + UserFileManagerImpl.ensureParentDirExists(file) assertThat(file.parentFile.exists()).isTrue() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt index e1007faae88b..858d0e7b94d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -35,6 +35,8 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.demomode.DemoMode +import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -50,10 +52,12 @@ import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_ import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusIconContainer +import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.VariableDateView import com.android.systemui.statusbar.policy.VariableDateViewController import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock @@ -104,7 +108,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { @Mock private lateinit var featureFlags: FeatureFlags @Mock - private lateinit var clock: TextView + private lateinit var clock: Clock @Mock private lateinit var date: VariableDateView @Mock @@ -138,6 +142,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { private lateinit var qsConstraints: ConstraintSet @Mock private lateinit var largeScreenConstraints: ConstraintSet + @Mock private lateinit var demoModeController: DemoModeController @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @@ -146,10 +151,12 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { private lateinit var controller: LargeScreenShadeHeaderController private lateinit var carrierIconSlots: List<String> private val configurationController = FakeConfigurationController() + private lateinit var demoModeControllerCapture: ArgumentCaptor<DemoMode> @Before fun setUp() { - whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock) + demoModeControllerCapture = argumentCaptor<DemoMode>() + whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock) whenever(clock.context).thenReturn(mockedContext) whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date) @@ -195,7 +202,8 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { dumpManager, featureFlags, qsCarrierGroupControllerBuilder, - combinedShadeHeadersConstraintManager + combinedShadeHeadersConstraintManager, + demoModeController ) whenever(view.isAttachedToWindow).thenReturn(true) controller.init() @@ -617,6 +625,21 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { } @Test + fun demoMode_attachDemoMode() { + verify(demoModeController).addCallback(capture(demoModeControllerCapture)) + demoModeControllerCapture.value.onDemoModeStarted() + verify(clock).onDemoModeStarted() + } + + @Test + fun demoMode_detachDemoMode() { + controller.simulateViewDetached() + verify(demoModeController).removeCallback(capture(demoModeControllerCapture)) + demoModeControllerCapture.value.onDemoModeFinished() + verify(clock).onDemoModeFinished() + } + + @Test fun animateOutOnStartCustomizing() { val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) val duration = 1000L diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt index 90ae693db955..b4c8f981b760 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt @@ -13,6 +13,8 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.demomode.DemoMode +import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -22,9 +24,12 @@ import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusIconContainer +import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.VariableDateViewController import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before @@ -52,7 +57,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder @Mock private lateinit var featureFlags: FeatureFlags - @Mock private lateinit var clock: TextView + @Mock private lateinit var clock: Clock @Mock private lateinit var date: TextView @Mock private lateinit var carrierGroup: QSCarrierGroup @Mock private lateinit var batteryMeterView: BatteryMeterView @@ -66,6 +71,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { CombinedShadeHeadersConstraintManager @Mock private lateinit var mockedContext: Context + @Mock private lateinit var demoModeController: DemoModeController @JvmField @Rule val mockitoRule = MockitoJUnit.rule() var viewVisibility = View.GONE @@ -76,7 +82,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { @Before fun setup() { - whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock) + whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock) whenever(clock.context).thenReturn(mockedContext) whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date) whenever(date.context).thenReturn(mockedContext) @@ -111,8 +117,9 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { dumpManager, featureFlags, qsCarrierGroupControllerBuilder, - combinedShadeHeadersConstraintManager - ) + combinedShadeHeadersConstraintManager, + demoModeController + ) whenever(view.isAttachedToWindow).thenReturn(true) mLargeScreenShadeHeaderController.init() carrierIconSlots = listOf( @@ -230,4 +237,21 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { verify(animator).setInterpolator(Interpolators.ALPHA_IN) verify(animator).start() } + + @Test + fun demoMode_attachDemoMode() { + val cb = argumentCaptor<DemoMode>() + verify(demoModeController).addCallback(capture(cb)) + cb.value.onDemoModeStarted() + verify(clock).onDemoModeStarted() + } + + @Test + fun demoMode_detachDemoMode() { + mLargeScreenShadeHeaderController.simulateViewDetached() + val cb = argumentCaptor<DemoMode>() + verify(demoModeController).removeCallback(capture(cb)) + cb.value.onDemoModeFinished() + verify(clock).onDemoModeFinished() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 42bc7948caf4..8aaa18129834 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -117,13 +117,6 @@ public class CommandQueueTest extends SysuiTestCase { } @Test - public void testCollapsePanels() { - mCommandQueue.animateCollapsePanels(); - waitForIdleSync(); - verify(mCallbacks).animateCollapsePanels(eq(0), eq(false)); - } - - @Test public void testExpandSettings() { String panel = "some_panel"; mCommandQueue.animateExpandSettingsPanel(panel); 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 15a687d2adc7..452606dfcca4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static android.content.Intent.ACTION_USER_SWITCHED; - import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -34,7 +32,6 @@ import android.app.Notification; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Handler; @@ -293,11 +290,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - public void testActionUserSwitchedCallsOnUserSwitched() { - Intent intent = new Intent() - .setAction(ACTION_USER_SWITCHED) - .putExtra(Intent.EXTRA_USER_HANDLE, mSecondaryUser.id); - mLockscreenUserManager.getBaseBroadcastReceiverForTest().onReceive(mContext, intent); + public void testUserSwitchedCallsOnUserSwitched() { + mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanged(mSecondaryUser.id, + mContext); verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id); } @@ -366,6 +361,10 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { return mBaseBroadcastReceiver; } + public UserTracker.Callback getUserTrackerCallbackForTest() { + return mUserChangedCallback; + } + public ContentObserver getLockscreenSettingsObserverForTest() { return mLockscreenSettingsObserver; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 8b7b4dea155f..6bd3f7a27413 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -26,22 +26,17 @@ import static android.app.NotificationManager.IMPORTANCE_MIN; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry; -import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -54,10 +49,10 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.CoreStartable; import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -97,7 +92,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private SysuiStatusBarStateController mStatusBarStateController; - @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock private UserTracker mUserTracker; private final FakeSettings mFakeSettings = new FakeSettings(); private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; @@ -117,7 +112,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { mKeyguardUpdateMonitor, mHighPriorityProvider, mStatusBarStateController, - mBroadcastDispatcher, + mUserTracker, mFakeSettings, mFakeSettings); mKeyguardNotificationVisibilityProvider = component.getProvider(); @@ -205,23 +200,19 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test - public void notifyListeners_onReceiveUserSwitchBroadcast() { - ArgumentCaptor<BroadcastReceiver> callbackCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mBroadcastDispatcher).registerReceiver( + public void notifyListeners_onReceiveUserSwitchCallback() { + ArgumentCaptor<UserTracker.Callback> callbackCaptor = + ArgumentCaptor.forClass(UserTracker.Callback.class); + verify(mUserTracker).addCallback( callbackCaptor.capture(), - argThat(intentFilter -> intentFilter.hasAction(Intent.ACTION_USER_SWITCHED)), - isNull(), - isNull(), - eq(Context.RECEIVER_EXPORTED), - isNull()); - BroadcastReceiver callback = callbackCaptor.getValue(); + any()); + UserTracker.Callback callback = callbackCaptor.getValue(); Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); - callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED)); + callback.onUserChanged(CURR_USER_ID, mContext); verify(listener).accept(anyString()); } @@ -619,7 +610,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor, @BindsInstance HighPriorityProvider highPriorityProvider, @BindsInstance SysuiStatusBarStateController statusBarStateController, - @BindsInstance BroadcastDispatcher broadcastDispatcher, + @BindsInstance UserTracker userTracker, @BindsInstance SecureSettings secureSettings, @BindsInstance GlobalSettings globalSettings ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java index ed2afe753a5e..915924f13197 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java @@ -41,7 +41,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger; -import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index bf31eb287579..3fccd37d9d7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -136,7 +136,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false); verify(mCentralSurfaces).updateQsExpansionEnabled(); - verify(mShadeController).animateCollapsePanels(); + verify(mShadeController).animateCollapseShade(); // Trying to open it does nothing. mSbcqCallbacks.animateExpandNotificationsPanel(); @@ -154,7 +154,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE, false); verify(mCentralSurfaces).updateQsExpansionEnabled(); - verify(mShadeController, never()).animateCollapsePanels(); + verify(mShadeController, never()).animateCollapseShade(); // Can now be opened. mSbcqCallbacks.animateExpandNotificationsPanel(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 013e7278753d..ed84e4268c90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -392,10 +392,21 @@ public class CentralSurfacesImplTest extends SysuiTestCase { return null; }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); - mShadeController = spy(new ShadeControllerImpl(mCommandQueue, - mStatusBarStateController, mNotificationShadeWindowController, - mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class), - () -> Optional.of(mCentralSurfaces), () -> mAssistManager)); + mShadeController = spy(new ShadeControllerImpl( + mCommandQueue, + mKeyguardStateController, + mStatusBarStateController, + mStatusBarKeyguardViewManager, + mStatusBarWindowController, + mNotificationShadeWindowController, + mContext.getSystemService(WindowManager.class), + () -> mAssistManager, + () -> mNotificationGutsManager + )); + mShadeController.setNotificationPanelViewController(mNotificationPanelViewController); + mShadeController.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); + mShadeController.setNotificationPresenter(mNotificationPresenter); when(mOperatorNameViewControllerFactory.create(any())) .thenReturn(mOperatorNameViewController); @@ -492,6 +503,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { return mViewRootImpl; } }; + mCentralSurfaces.initShadeVisibilityListener(); when(mViewRootImpl.getOnBackInvokedDispatcher()) .thenReturn(mOnBackInvokedDispatcher); when(mKeyguardViewMediator.registerCentralSurfaces( @@ -807,7 +819,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true); mOnBackInvokedCallback.getValue().onBackInvoked(); - verify(mShadeController).animateCollapsePanels(); + verify(mShadeController).animateCollapseShade(); } @Test @@ -1030,7 +1042,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - public void collapseShade_callsAnimateCollapsePanels_whenExpanded() { + public void collapseShade_callsanimateCollapseShade_whenExpanded() { // GIVEN the shade is expanded mCentralSurfaces.onShadeExpansionFullyChanged(true); mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); @@ -1038,12 +1050,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // WHEN collapseShade is called mCentralSurfaces.collapseShade(); - // VERIFY that animateCollapsePanels is called - verify(mShadeController).animateCollapsePanels(); + // VERIFY that animateCollapseShade is called + verify(mShadeController).animateCollapseShade(); } @Test - public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() { + public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() { // GIVEN the shade is collapsed mCentralSurfaces.onShadeExpansionFullyChanged(false); mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); @@ -1051,12 +1063,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // WHEN collapseShade is called mCentralSurfaces.collapseShade(); - // VERIFY that animateCollapsePanels is NOT called - verify(mShadeController, never()).animateCollapsePanels(); + // VERIFY that animateCollapseShade is NOT called + verify(mShadeController, never()).animateCollapseShade(); } @Test - public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() { + public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() { // GIVEN the shade is expanded & flag enabled mCentralSurfaces.onShadeExpansionFullyChanged(true); mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); @@ -1065,12 +1077,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // WHEN collapseShadeForBugreport is called mCentralSurfaces.collapseShadeForBugreport(); - // VERIFY that animateCollapsePanels is called - verify(mShadeController).animateCollapsePanels(); + // VERIFY that animateCollapseShade is called + verify(mShadeController).animateCollapseShade(); } @Test - public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() { + public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() { // GIVEN the shade is expanded & flag enabled mCentralSurfaces.onShadeExpansionFullyChanged(true); mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); @@ -1079,8 +1091,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // WHEN collapseShadeForBugreport is called mCentralSurfaces.collapseShadeForBugreport(); - // VERIFY that animateCollapsePanels is called - verify(mShadeController, never()).animateCollapsePanels(); + // VERIFY that animateCollapseShade is called + verify(mShadeController, never()).animateCollapseShade(); } @Test 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 bf5186b6324d..e467d9399059 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 @@ -307,6 +307,17 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() { + when(mKeyguardStateController.isOccluded()).thenReturn(true); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + expansionEvent( + /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* expanded= */ true, + /* tracking= */ false)); + verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); + } + + @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() { // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index ce54d784520c..cae414a3dc67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -263,7 +263,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { while (!runnables.isEmpty()) runnables.remove(0).run(); // Then - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(), eq(false) /* animate */, any(), any()); @@ -296,7 +296,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry())); // This is called regardless, and simply short circuits when there is nothing to do. - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mAssistManager).hideAssist(); @@ -329,7 +329,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry())); - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mAssistManager).hideAssist(); @@ -357,7 +357,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry()); - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mAssistManager).hideAssist(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt index b7a6c0125cfa..d35ce76d7a9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt @@ -22,7 +22,7 @@ import android.os.UserHandle import android.provider.Settings.Global import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -45,7 +45,7 @@ class AirplaneModeRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: AirplaneModeRepositoryImpl - @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var logger: TableLogBuffer private lateinit var bgHandler: Handler private lateinit var scope: CoroutineScope private lateinit var settings: FakeSettings diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt index 7df707789290..6bfc2f1a8b8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt @@ -51,15 +51,11 @@ class PairwiseFlowTest : SysuiTestCase() { ) } - @Test - fun notEnough() = runBlocking { - assertThatFlow(flowOf(1).pairwise()).emitsNothing() - } + @Test fun notEnough() = runBlocking { assertThatFlow(flowOf(1).pairwise()).emitsNothing() } @Test fun withInit() = runBlocking { - assertThatFlow(flowOf(2).pairwise(initialValue = 1)) - .emitsExactly(WithPrev(1, 2)) + assertThatFlow(flowOf(2).pairwise(initialValue = 1)).emitsExactly(WithPrev(1, 2)) } @Test @@ -68,25 +64,78 @@ class PairwiseFlowTest : SysuiTestCase() { } @Test - fun withStateFlow() = runBlocking(Dispatchers.Main.immediate) { - val state = MutableStateFlow(1) - val stop = MutableSharedFlow<Unit>() - - val stoppable = merge(state, stop) - .takeWhile { it is Int } - .filterIsInstance<Int>() + fun withTransform() = runBlocking { + assertThatFlow( + flowOf("val1", "val2", "val3").pairwiseBy { prev: String, next: String -> + "$prev|$next" + } + ) + .emitsExactly("val1|val2", "val2|val3") + } - val job1 = launch { - assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2)) - } - state.value = 2 - val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() } + @Test + fun withGetInit() = runBlocking { + var initRun = false + assertThatFlow( + flowOf("val1", "val2").pairwiseBy( + getInitialValue = { + initRun = true + "initial" + } + ) { prev: String, next: String -> "$prev|$next" } + ) + .emitsExactly("initial|val1", "val1|val2") + assertThat(initRun).isTrue() + } - stop.emit(Unit) + @Test + fun notEnoughWithGetInit() = runBlocking { + var initRun = false + assertThatFlow( + emptyFlow<String>().pairwiseBy( + getInitialValue = { + initRun = true + "initial" + } + ) { prev: String, next: String -> "$prev|$next" } + ) + .emitsNothing() + // Even though the flow will not emit anything, the initial value function should still get + // run. + assertThat(initRun).isTrue() + } - assertThatJob(job1).isCompleted() - assertThatJob(job2).isCompleted() + @Test + fun getInitNotRunWhenFlowNotCollected() = runBlocking { + var initRun = false + flowOf("val1", "val2").pairwiseBy( + getInitialValue = { + initRun = true + "initial" + } + ) { prev: String, next: String -> "$prev|$next" } + + // Since the flow isn't collected, ensure [initialValueFun] isn't run. + assertThat(initRun).isFalse() } + + @Test + fun withStateFlow() = + runBlocking(Dispatchers.Main.immediate) { + val state = MutableStateFlow(1) + val stop = MutableSharedFlow<Unit>() + + val stoppable = merge(state, stop).takeWhile { it is Int }.filterIsInstance<Int>() + + val job1 = launch { assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2)) } + state.value = 2 + val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() } + + stop.emit(Unit) + + assertThatJob(job1).isCompleted() + assertThatJob(job2).isCompleted() + } } @SmallTest @@ -94,18 +143,17 @@ class PairwiseFlowTest : SysuiTestCase() { class SetChangesFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { - assertThatFlow( - flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges() - ).emitsExactly( - SetChanges( - added = setOf(1, 2, 3), - removed = emptySet(), - ), - SetChanges( - added = setOf(4), - removed = setOf(1), - ), - ) + assertThatFlow(flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges()) + .emitsExactly( + SetChanges( + added = setOf(1, 2, 3), + removed = emptySet(), + ), + SetChanges( + added = setOf(4), + removed = setOf(1), + ), + ) } @Test @@ -147,14 +195,19 @@ class SetChangesFlowTest : SysuiTestCase() { class SampleFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { - assertThatFlow(flow { yield(); emit(1) }.sample(flowOf(2)) { a, b -> a to b }) + assertThatFlow( + flow { + yield() + emit(1) + } + .sample(flowOf(2)) { a, b -> a to b } + ) .emitsExactly(1 to 2) } @Test fun otherFlowNoValueYet() = runBlocking { - assertThatFlow(flowOf(1).sample(emptyFlow<Unit>())) - .emitsNothing() + assertThatFlow(flowOf(1).sample(emptyFlow<Unit>())).emitsNothing() } @Test @@ -178,13 +231,14 @@ class SampleFlowTest : SysuiTestCase() { } } -private fun <T> assertThatFlow(flow: Flow<T>) = object { - suspend fun emitsExactly(vararg emissions: T) = - assertThat(flow.toList()).containsExactly(*emissions).inOrder() - suspend fun emitsNothing() = - assertThat(flow.toList()).isEmpty() -} +private fun <T> assertThatFlow(flow: Flow<T>) = + object { + suspend fun emitsExactly(vararg emissions: T) = + assertThat(flow.toList()).containsExactly(*emissions).inOrder() + suspend fun emitsNothing() = assertThat(flow.toList()).isEmpty() + } -private fun assertThatJob(job: Job) = object { - fun isCompleted() = assertThat(job.isCompleted).isTrue() -} +private fun assertThatJob(job: Job) = + object { + fun isCompleted() = assertThat(job.isCompleted).isTrue() + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java index f6fd2cb8f3b1..f68baf5546d7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java @@ -16,32 +16,71 @@ package com.android.systemui.utils.leaks; import android.testing.LeakCheck; +import androidx.annotation.VisibleForTesting; + import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener; +import java.util.ArrayList; +import java.util.List; + public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener> implements FlashlightController { + + private final List<FlashlightListener> callbacks = new ArrayList<>(); + + @VisibleForTesting + public boolean isAvailable; + @VisibleForTesting + public boolean isEnabled; + @VisibleForTesting + public boolean hasFlashlight; + public FakeFlashlightController(LeakCheck test) { super(test, "flashlight"); } + @VisibleForTesting + public void onFlashlightAvailabilityChanged(boolean newValue) { + callbacks.forEach( + flashlightListener -> flashlightListener.onFlashlightAvailabilityChanged(newValue) + ); + } + + @VisibleForTesting + public void onFlashlightError() { + callbacks.forEach(FlashlightListener::onFlashlightError); + } + @Override public boolean hasFlashlight() { - return false; + return hasFlashlight; } @Override public void setFlashlight(boolean newState) { - + callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState)); } @Override public boolean isAvailable() { - return false; + return isAvailable; } @Override public boolean isEnabled() { - return false; + return isEnabled; + } + + @Override + public void addCallback(FlashlightListener listener) { + super.addCallback(listener); + callbacks.add(listener); + } + + @Override + public void removeCallback(FlashlightListener listener) { + super.removeCallback(listener); + callbacks.remove(listener); } } diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index 104d10de93c8..3487613d313c 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -19,6 +19,7 @@ package com.android.server; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.database.ContentObserver; import android.media.AudioManager; import android.media.Ringtone; import android.media.RingtoneManager; @@ -73,6 +74,7 @@ final class DockObserver extends SystemService { private final boolean mAllowTheaterModeWakeFromDock; private final List<ExtconStateConfig> mExtconStateConfigs; + private DeviceProvisionedObserver mDeviceProvisionedObserver; static final class ExtconStateProvider { private final Map<String, String> mState; @@ -110,7 +112,7 @@ final class DockObserver extends SystemService { Slog.w(TAG, "No state file found at: " + stateFilePath); return new ExtconStateProvider(new HashMap<>()); } catch (Exception e) { - Slog.e(TAG, "" , e); + Slog.e(TAG, "", e); return new ExtconStateProvider(new HashMap<>()); } } @@ -136,7 +138,7 @@ final class DockObserver extends SystemService { private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) { String[] rows = context.getResources().getStringArray( - com.android.internal.R.array.config_dockExtconStateMapping); + com.android.internal.R.array.config_dockExtconStateMapping); try { ArrayList<ExtconStateConfig> configs = new ArrayList<>(); for (String row : rows) { @@ -167,6 +169,7 @@ final class DockObserver extends SystemService { com.android.internal.R.bool.config_allowTheaterModeWakeFromDock); mKeepDreamingWhenUndocking = context.getResources().getBoolean( com.android.internal.R.bool.config_keepDreamingWhenUndocking); + mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler); mExtconStateConfigs = loadExtconStateConfigs(context); @@ -199,15 +202,19 @@ final class DockObserver extends SystemService { if (phase == PHASE_ACTIVITY_MANAGER_READY) { synchronized (mLock) { mSystemReady = true; - - // don't bother broadcasting undocked here - if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - updateLocked(); - } + mDeviceProvisionedObserver.onSystemReady(); + updateIfDockedLocked(); } } } + private void updateIfDockedLocked() { + // don't bother broadcasting undocked here + if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + updateLocked(); + } + } + private void setActualDockStateLocked(int newState) { mActualDockState = newState; if (!mUpdatesStopped) { @@ -252,8 +259,7 @@ final class DockObserver extends SystemService { // Skip the dock intent if not yet provisioned. final ContentResolver cr = getContext().getContentResolver(); - if (Settings.Global.getInt(cr, - Settings.Global.DEVICE_PROVISIONED, 0) == 0) { + if (!mDeviceProvisionedObserver.isDeviceProvisioned()) { Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); return; } @@ -302,6 +308,7 @@ final class DockObserver extends SystemService { getContext(), soundUri); if (sfx != null) { sfx.setStreamType(AudioManager.STREAM_SYSTEM); + sfx.preferBuiltinDevice(true); sfx.play(); } } @@ -418,4 +425,48 @@ final class DockObserver extends SystemService { } } } + + private final class DeviceProvisionedObserver extends ContentObserver { + private boolean mRegistered; + + public DeviceProvisionedObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + synchronized (mLock) { + updateRegistration(); + if (isDeviceProvisioned()) { + // Send the dock broadcast if device is docked after provisioning. + updateIfDockedLocked(); + } + } + } + + void onSystemReady() { + updateRegistration(); + } + + private void updateRegistration() { + boolean register = !isDeviceProvisioned(); + if (register == mRegistered) { + return; + } + final ContentResolver resolver = getContext().getContentResolver(); + if (register) { + resolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, this); + } else { + resolver.unregisterContentObserver(this); + } + mRegistered = register; + } + + boolean isDeviceProvisioned() { + return Settings.Global.getInt(getContext().getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) != 0; + } + } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4a01c61830d0..e8f25490e61a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3599,6 +3599,18 @@ public class AudioService extends IAudioService.Stub } } + // TODO enforce MODIFY_AUDIO_SYSTEM_SETTINGS when defined + private void enforceModifyAudioRoutingOrSystemSettingsPermission() { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + != PackageManager.PERMISSION_GRANTED + /*&& mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + != PackageManager.PERMISSION_DENIED*/) { + throw new SecurityException( + "Missing MODIFY_AUDIO_ROUTING or MODIFY_AUDIO_SYSTEM_SETTINGS permission"); + } + } + private void enforceAccessUltrasoundPermission() { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND) != PackageManager.PERMISSION_GRANTED) { @@ -3703,10 +3715,12 @@ public class AudioService extends IAudioService.Stub } /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes) - * Part of service interface, check permissions and parameters here */ + * Part of service interface, check permissions and parameters here + * Note calling package is for logging purposes only, not to be trusted + */ public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada, - @NonNull String callingPackage, @Nullable String attributionTag) { - enforceModifyAudioRoutingPermission(); + @NonNull String callingPackage) { + enforceModifyAudioRoutingOrSystemSettingsPermission(); Objects.requireNonNull(vi); Objects.requireNonNull(ada); Objects.requireNonNull(callingPackage); @@ -3719,8 +3733,20 @@ public class AudioService extends IAudioService.Stub return; } int index = vi.getVolumeIndex(); - if (index == VolumeInfo.INDEX_NOT_SET) { - throw new IllegalArgumentException("changing device volume requires a volume index"); + if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) { + throw new IllegalArgumentException( + "changing device volume requires a volume index or mute command"); + } + + // TODO handle unmuting of current audio device + // if a stream is not muted but the VolumeInfo is for muting, set the volume index + // for the device to min volume + if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(vi.getStreamType())) { + setStreamVolumeWithAttributionInt(vi.getStreamType(), + mStreamStates[vi.getStreamType()].getMinIndex(), + /*flags*/ 0, + ada, callingPackage, null); + return; } if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET @@ -3742,7 +3768,7 @@ public class AudioService extends IAudioService.Stub } } setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0, - ada, callingPackage, attributionTag); + ada, callingPackage, null); } /** Retain API for unsupported app usage */ @@ -4648,6 +4674,40 @@ public class AudioService extends IAudioService.Stub } } + /** + * @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes) + */ + public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi, + @NonNull AudioDeviceAttributes ada, @NonNull String callingPackage) { + enforceModifyAudioRoutingOrSystemSettingsPermission(); + Objects.requireNonNull(vi); + Objects.requireNonNull(ada); + Objects.requireNonNull(callingPackage); + if (!vi.hasStreamType()) { + Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception()); + return getDefaultVolumeInfo(); + } + + int streamType = vi.getStreamType(); + final VolumeInfo.Builder vib = new VolumeInfo.Builder(vi); + vib.setMinVolumeIndex((mStreamStates[streamType].mIndexMin + 5) / 10); + vib.setMaxVolumeIndex((mStreamStates[streamType].mIndexMax + 5) / 10); + synchronized (VolumeStreamState.class) { + final int index; + if (isFixedVolumeDevice(ada.getInternalType())) { + index = (mStreamStates[streamType].mIndexMax + 5) / 10; + } else { + index = (mStreamStates[streamType].getIndex(ada.getInternalType()) + 5) / 10; + } + vib.setVolumeIndex(index); + // only set as a mute command if stream muted + if (mStreamStates[streamType].mIsMuted) { + vib.setMuted(true); + } + return vib.build(); + } + } + /** @see AudioManager#getStreamMaxVolume(int) */ public int getStreamMaxVolume(int streamType) { ensureValidStreamType(streamType); @@ -4686,7 +4746,6 @@ public class AudioService extends IAudioService.Stub sDefaultVolumeInfo = new VolumeInfo.Builder(AudioSystem.STREAM_MUSIC) .setMinVolumeIndex(getStreamMinVolume(AudioSystem.STREAM_MUSIC)) .setMaxVolumeIndex(getStreamMaxVolume(AudioSystem.STREAM_MUSIC)) - .setMuted(false) .build(); } return sDefaultVolumeInfo; @@ -6996,9 +7055,10 @@ public class AudioService extends IAudioService.Stub private @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) { - // translate Java device type to native device type (for the devices masks for full / fixed) - final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice( - device.getType()); + // Get the internal type set by the AudioDeviceAttributes constructor which is always more + // exact (avoids double conversions) than a conversion from SDK type via + // AudioDeviceInfo.convertDeviceTypeToInternalDevice() + final int audioSystemDeviceOut = device.getInternalType(); int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut); if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a92b65ebeb6f..4d44c886fa22 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3796,13 +3796,13 @@ public class NotificationManagerService extends SystemService { } private void createNotificationChannelsImpl(String pkg, int uid, - ParceledListSlice channelsList, boolean fromTargetApp) { - createNotificationChannelsImpl(pkg, uid, channelsList, fromTargetApp, + ParceledListSlice channelsList) { + createNotificationChannelsImpl(pkg, uid, channelsList, ActivityTaskManager.INVALID_TASK_ID); } private void createNotificationChannelsImpl(String pkg, int uid, - ParceledListSlice channelsList, boolean fromTargetApp, int startingTaskId) { + ParceledListSlice channelsList, int startingTaskId) { List<NotificationChannel> channels = channelsList.getList(); final int channelsSize = channels.size(); ParceledListSlice<NotificationChannel> oldChannels = @@ -3814,7 +3814,7 @@ public class NotificationManagerService extends SystemService { final NotificationChannel channel = channels.get(i); Objects.requireNonNull(channel, "channel in list is null"); needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid, - channel, fromTargetApp, + channel, true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed( pkg, UserHandle.getUserId(uid))); if (needsPolicyFileChange) { @@ -3850,7 +3850,6 @@ public class NotificationManagerService extends SystemService { @Override public void createNotificationChannels(String pkg, ParceledListSlice channelsList) { checkCallerIsSystemOrSameApp(pkg); - boolean fromTargetApp = !isCallerSystemOrPhone(); // if not system, it's from the app int taskId = ActivityTaskManager.INVALID_TASK_ID; try { int uid = mPackageManager.getPackageUid(pkg, 0, @@ -3859,15 +3858,14 @@ public class NotificationManagerService extends SystemService { } catch (RemoteException e) { // Do nothing } - createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, fromTargetApp, - taskId); + createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId); } @Override public void createNotificationChannelsForPackage(String pkg, int uid, ParceledListSlice channelsList) { enforceSystemOrSystemUI("only system can call this"); - createNotificationChannelsImpl(pkg, uid, channelsList, false /* fromTargetApp */); + createNotificationChannelsImpl(pkg, uid, channelsList); } @Override @@ -3882,8 +3880,7 @@ public class NotificationManagerService extends SystemService { CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId)); conversationChannel.setConversationId(parentId, conversationId); createNotificationChannelsImpl( - pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)), - false /* fromTargetApp */); + pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel))); mRankingHandler.requestSort(); handleSavePolicyFile(); } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 97911581c59c..d8aa469bcd81 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -916,7 +916,7 @@ public class PreferencesHelper implements RankingConfig { throw new IllegalArgumentException("Reserved id"); } NotificationChannel existing = r.channels.get(channel.getId()); - if (existing != null) { + if (existing != null && fromTargetApp) { // Actually modifying an existing channel - keep most of the existing settings if (existing.isDeleted()) { // The existing channel was deleted - undelete it. @@ -1002,7 +1002,9 @@ public class PreferencesHelper implements RankingConfig { } if (fromTargetApp) { channel.setLockscreenVisibility(r.visibility); - channel.setAllowBubbles(NotificationChannel.DEFAULT_ALLOW_BUBBLE); + channel.setAllowBubbles(existing != null + ? existing.getAllowBubbles() + : NotificationChannel.DEFAULT_ALLOW_BUBBLE); } clearLockedFieldsLocked(channel); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 0d031333443e..b1c986e6558a 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -2197,6 +2197,15 @@ public final class PowerManagerService extends SystemService if (sQuiescent) { mDirty |= DIRTY_QUIESCENT; } + PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP); + if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) { + // Workaround for b/187231320 where the AOD can get stuck in a "half on / + // half off" state when a non-default-group VirtualDisplay causes the global + // wakefulness to change to awake, even though the default display is + // dozing. We set sandman summoned to restart dreaming to get it unstuck. + // TODO(b/255688811) - fix this so that AOD never gets interrupted at all. + defaultGroup.setSandmanSummonedLocked(true); + } break; case WAKEFULNESS_ASLEEP: diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 4b8c7c176fda..36293d518f51 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -107,6 +107,7 @@ public class TrustAgentWrapper { // Trust state private boolean mTrusted; private boolean mWaitingForTrustableDowngrade = false; + private boolean mWithinSecurityLockdownWindow = false; private boolean mTrustable; private CharSequence mMessage; private boolean mDisplayTrustGrantedMessage; @@ -160,6 +161,7 @@ public class TrustAgentWrapper { mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0; if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) { mWaitingForTrustableDowngrade = true; + setSecurityWindowTimer(); } else { mWaitingForTrustableDowngrade = false; } @@ -452,6 +454,9 @@ public class TrustAgentWrapper { if (mBound) { scheduleRestart(); } + if (mWithinSecurityLockdownWindow) { + mTrustManagerService.lockUser(mUserId); + } // mTrustDisabledByDpm maintains state } }; @@ -673,6 +678,22 @@ public class TrustAgentWrapper { } } + private void setSecurityWindowTimer() { + mWithinSecurityLockdownWindow = true; + long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds + mAlarmManager.setExact( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + expiration, + TAG, + new AlarmManager.OnAlarmListener() { + @Override + public void onAlarm() { + mWithinSecurityLockdownWindow = false; + } + }, + Handler.getMain()); + } + public boolean isManagingTrust() { return mManagingTrust && !mTrustDisabledByDpm; } @@ -691,7 +712,6 @@ public class TrustAgentWrapper { public void destroy() { mHandler.removeMessages(MSG_RESTART_TIMEOUT); - if (!mBound) { return; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 3a936a5d7378..576296e0873c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3353,7 +3353,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); - controller.mTransitionMetricsReporter.associate(t, + controller.mTransitionMetricsReporter.associate(t.getToken(), startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); startAsyncRotation(false /* shouldDebounce */); } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 74a236bd862c..6edb63c14c64 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -87,10 +87,6 @@ final class LetterboxUiController { private final LetterboxConfiguration mLetterboxConfiguration; private final ActivityRecord mActivityRecord; - // Taskbar expanded height. Used to determine whether to crop an app window to display rounded - // corners above the taskbar. - private final float mExpandedTaskBarHeight; - private boolean mShowWallpaperForLetterboxBackground; @Nullable @@ -102,8 +98,6 @@ final class LetterboxUiController { // is created in its constructor. It shouldn't be used in this constructor but it's safe // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; - mExpandedTaskBarHeight = - getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); } /** Cleans up {@link Letterbox} if it exists.*/ @@ -285,14 +279,17 @@ final class LetterboxUiController { } float getSplitScreenAspectRatio() { + // Getting the same aspect ratio that apps get in split screen. + final DisplayContent displayContent = mActivityRecord.getDisplayContent(); + if (displayContent == null) { + return getDefaultMinAspectRatioForUnresizableApps(); + } int dividerWindowWidth = getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness); int dividerInsets = getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); int dividerSize = dividerWindowWidth - dividerInsets * 2; - - // Getting the same aspect ratio that apps get in split screen. - Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds()); + final Rect bounds = new Rect(displayContent.getBounds()); if (bounds.width() >= bounds.height()) { bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); bounds.right = bounds.centerX(); @@ -555,7 +552,6 @@ final class LetterboxUiController { final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); return taskbarInsetsSource != null - && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight && taskbarInsetsSource.isVisible(); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index b6e52aaff035..7ce17d422386 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -98,6 +98,7 @@ import com.android.server.wm.utils.RotationAnimationUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -107,7 +108,7 @@ import java.util.function.Predicate; * Represents a logical transition. * @see TransitionController */ -class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener { +class Transition implements BLASTSyncEngine.TransactionReadyListener { private static final String TAG = "Transition"; private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition"; @@ -158,6 +159,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private @TransitionFlags int mFlags; private final TransitionController mController; private final BLASTSyncEngine mSyncEngine; + private final Token mToken; private RemoteTransition mRemoteTransition = null; /** Only use for clean-up after binder death! */ @@ -220,10 +222,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mFlags = flags; mController = controller; mSyncEngine = syncEngine; + mToken = new Token(this); controller.mTransitionTracer.logState(this); } + @Nullable + static Transition fromBinder(@NonNull IBinder token) { + try { + return ((Token) token).mTransition.get(); + } catch (ClassCastException e) { + Slog.w(TAG, "Invalid transition token: " + token, e); + return null; + } + } + + @NonNull + IBinder getToken() { + return mToken; + } + void addFlag(int flag) { mFlags |= flag; } @@ -733,6 +751,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); } + // Close the transactions now. They were originally copied to Shell in case we needed to + // apply them due to a remote failure. Since we don't need to apply them anymore, free them + // immediately. + if (mStartTransaction != null) mStartTransaction.close(); + if (mFinishTransaction != null) mFinishTransaction.close(); mStartTransaction = mFinishTransaction = null; if (mState < STATE_PLAYING) { throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); @@ -874,6 +897,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); } + cleanUpInternal(); } void abort() { @@ -916,6 +940,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe dc.getPendingTransaction().merge(transaction); mSyncId = -1; mOverrideOptions = null; + cleanUpInternal(); return; } // Ensure that wallpaper visibility is updated with the latest wallpaper target. @@ -1034,7 +1059,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Calling onTransitionReady: %s", info); mController.getTransitionPlayer().onTransitionReady( - this, info, transaction, mFinishTransaction); + mToken, info, transaction, mFinishTransaction); + // Since we created root-leash but no longer reference it from core, release it now + info.releaseAnimSurfaces(); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); @@ -1067,7 +1094,17 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (mFinishTransaction != null) { mFinishTransaction.apply(); } - mController.finishTransition(this); + mController.finishTransition(mToken); + } + + private void cleanUpInternal() { + // Clean-up any native references. + for (int i = 0; i < mChanges.size(); ++i) { + final ChangeInfo ci = mChanges.valueAt(i); + if (ci.mSnapshot != null) { + ci.mSnapshot.release(); + } + } } /** @see RecentsAnimationController#attachNavigationBarToApp */ @@ -1850,10 +1887,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return isCollecting() && mSyncId >= 0; } - static Transition fromBinder(IBinder binder) { - return (Transition) binder; - } - @VisibleForTesting static class ChangeInfo { private static final int FLAG_NONE = 0; @@ -2345,4 +2378,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } } + + private static class Token extends Binder { + final WeakReference<Transition> mTransition; + + Token(Transition transition) { + mTransition = new WeakReference<>(transition); + } + + @Override + public String toString() { + return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " " + + mTransition.get() + "}"; + } + } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index e4d39b94ab10..d3d1c163aa19 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -458,8 +458,9 @@ class TransitionController { info = new ActivityManager.RunningTaskInfo(); startTask.fillTaskInfo(info); } - mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo( - transition.mType, info, remoteTransition, displayChange)); + mTransitionPlayer.requestStartTransition(transition.getToken(), + new TransitionRequestInfo(transition.mType, info, remoteTransition, + displayChange)); transition.setRemoteTransition(remoteTransition); } catch (RemoteException e) { Slog.e(TAG, "Error requesting transition", e); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index b30fd0729525..d85bd830cc11 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -307,7 +307,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub nextTransition.setAllReady(); } }); - return nextTransition; + return nextTransition.getToken(); } transition = mTransitionController.createTransition(type); } @@ -316,7 +316,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (needsSetReady) { transition.setAllReady(); } - return transition; + return transition.getToken(); } } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java index c325778a5683..ee09074f7625 100644 --- a/services/tests/servicestests/src/com/android/server/DockObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Intent; import android.os.Looper; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; @@ -74,6 +75,11 @@ public class DockObserverTest { .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED); } + void setDeviceProvisioned(boolean provisioned) { + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, + provisioned ? 1 : 0); + } + @Before public void setUp() { if (Looper.myLooper() == null) { @@ -131,4 +137,25 @@ public class DockObserverTest { assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5", Intent.EXTRA_DOCK_STATE_HE_DESK); } + + @Test + public void testDockIntentBroadcast_deviceNotProvisioned() + throws ExecutionException, InterruptedException { + DockObserver observer = new DockObserver(mInterceptingContext); + // Set the device as not provisioned. + setDeviceProvisioned(false); + observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); + + BroadcastInterceptingContext.FutureIntent futureIntent = + updateExtconDockState(observer, "DOCK=1"); + TestableLooper.get(this).processAllMessages(); + // Verify no broadcast was sent as device was not provisioned. + futureIntent.assertNotReceived(); + + // Ensure we send the broadcast when the device is provisioned. + setDeviceProvisioned(true); + TestableLooper.get(this).processAllMessages(); + assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1)) + .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK); + } } diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java index 7acb6d66c834..64af2969b67c 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java @@ -27,18 +27,18 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.VolumeInfo; import android.os.test.TestLooper; +import android.util.Log; import androidx.test.InstrumentationRegistry; +import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; public class AudioDeviceVolumeManagerTest { private static final String TAG = "AudioDeviceVolumeManagerTest"; - private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); - private Context mContext; private String mPackageName; private AudioSystemAdapter mSpyAudioSystem; @@ -84,14 +84,20 @@ public class AudioDeviceVolumeManagerTest { final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes( /*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla"); - mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName, TAG); + mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName); mTestLooper.dispatchAll(); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); - mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName, TAG); + mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName); mTestLooper.dispatchAll(); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + + final VolumeInfo vi = mAudioService.getDeviceVolume(volMin, usbDevice, mPackageName); + Assert.assertEquals("getDeviceVolume doesn't return expected value in " + vi + + " after setting " + volMid, + (volMid.getMaxVolumeIndex() - volMid.getMinVolumeIndex()) / 2, + (vi.getMaxVolumeIndex() - vi.getMinVolumeIndex()) / 2); } } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index a42d009c417c..6325008dc1e0 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -1929,6 +1929,50 @@ public class PowerManagerServiceTest { } @Test + public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() { + final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; + final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1; + final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = + new AtomicReference<>(); + doAnswer((Answer<Void>) invocation -> { + listener.set(invocation.getArgument(0)); + return null; + }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any()); + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = nonDefaultDisplayGroupId; + when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + createService(); + startSystem(); + + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( + WAKEFULNESS_AWAKE); + + forceDozing(); + advanceTime(500); + + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( + WAKEFULNESS_DOZING); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING); + verify(mDreamManagerInternalMock).startDream(eq(true), anyString()); + + listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId); + advanceTime(500); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo( + WAKEFULNESS_AWAKE); + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( + WAKEFULNESS_DOZING); + verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString()); + } + + @Test public void testLastSleepTime_notUpdatedWhenDreaming() { createService(); startSystem(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 077caa408344..3f3b052931ab 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -1101,10 +1101,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { new NotificationChannel("id", "name", IMPORTANCE_HIGH); mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel); - // pretend only this following part is called by the app (system permissions are required to - // update the notification channel on behalf of the user above) - mService.isSystemUid = false; - // Recreating with a lower importance leaves channel unchanged. final NotificationChannel dupeChannel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW); @@ -1130,46 +1126,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCreateNotificationChannels_fromAppCannotSetFields() throws Exception { - // Confirm that when createNotificationChannels is called from the relevant app and not - // system, then it cannot set fields that can't be set by apps - mService.isSystemUid = false; - - final NotificationChannel channel = - new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); - channel.setBypassDnd(true); - channel.setAllowBubbles(true); - - mBinderService.createNotificationChannels(PKG, - new ParceledListSlice(Arrays.asList(channel))); - - final NotificationChannel createdChannel = - mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id"); - assertFalse(createdChannel.canBypassDnd()); - assertFalse(createdChannel.canBubble()); - } - - @Test - public void testCreateNotificationChannels_fromSystemCanSetFields() throws Exception { - // Confirm that when createNotificationChannels is called from system, - // then it can set fields that can't be set by apps - mService.isSystemUid = true; - - final NotificationChannel channel = - new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); - channel.setBypassDnd(true); - channel.setAllowBubbles(true); - - mBinderService.createNotificationChannels(PKG, - new ParceledListSlice(Arrays.asList(channel))); - - final NotificationChannel createdChannel = - mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id"); - assertTrue(createdChannel.canBypassDnd()); - assertTrue(createdChannel.canBubble()); - } - - @Test public void testBlockedNotifications_suspended() throws Exception { when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true); @@ -3132,8 +3088,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testDeleteChannelGroupChecksForFgses() throws Exception { - // the setup for this test requires it to seem like it's coming from the app - mService.isSystemUid = false; when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) .thenReturn(singletonList(mock(AssociationInfo.class))); CountDownLatch latch = new CountDownLatch(2); @@ -3146,7 +3100,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ParceledListSlice<NotificationChannel> pls = new ParceledListSlice(ImmutableList.of(notificationChannel)); try { - mBinderService.createNotificationChannels(PKG, pls); + mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -3165,10 +3119,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ParceledListSlice<NotificationChannel> pls = new ParceledListSlice(ImmutableList.of(notificationChannel)); try { - // Because existing channels won't have their groups overwritten when the call - // is from the app, this call won't take the channel out of the group - mBinderService.createNotificationChannels(PKG, pls); - mBinderService.deleteNotificationChannelGroup(PKG, "group"); + mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls); + mBinderService.deleteNotificationChannelGroup(PKG, "group"); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -8673,7 +8625,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals("friend", friendChannel.getConversationId()); assertEquals(null, original.getConversationId()); assertEquals(original.canShowBadge(), friendChannel.canShowBadge()); - assertEquals(original.canBubble(), friendChannel.canBubble()); // called by system + assertFalse(friendChannel.canBubble()); // can't be modified by app assertFalse(original.getId().equals(friendChannel.getId())); assertNotNull(friendChannel.getId()); } 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 63335086859d..9df4a40a8031 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -369,7 +369,7 @@ public class WallpaperControllerTests extends WindowTestsBase { final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); token.finishSync(t, false /* cancel */); transit.onTransactionReady(transit.getSyncId(), t); - dc.mTransitionController.finishTransition(transit); + dc.mTransitionController.finishTransition(transit.getToken()); assertFalse(wallpaperWindow.isVisible()); assertFalse(token.isVisible()); 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 b99fd1606f55..894ba3e95261 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1731,7 +1731,7 @@ class WindowTestsBase extends SystemServiceTestsBase { } void startTransition() { - mOrganizer.startTransition(mLastTransit, null); + mOrganizer.startTransition(mLastTransit.getToken(), null); } void onTransactionReady(SurfaceControl.Transaction t) { @@ -1744,7 +1744,7 @@ class WindowTestsBase extends SystemServiceTestsBase { } public void finish() { - mController.finishTransition(mLastTransit); + mController.finishTransition(mLastTransit.getToken()); } } } |