diff options
178 files changed, 4869 insertions, 1716 deletions
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java index e5a06c9bd146..3c361d772d3d 100644 --- a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java +++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java @@ -16,12 +16,14 @@ package android.graphics.perftests; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Color; +import android.graphics.ColorSpace; import android.graphics.ImageDecoder; import android.graphics.Paint; import android.graphics.RecordingCanvas; @@ -104,15 +106,36 @@ public class CanvasPerfTest { } @Test - public void testCreateScaledBitmap() throws IOException { + public void testCreateScaledSrgbBitmap() throws IOException { BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final Context context = InstrumentationRegistry.getContext(); Bitmap source = ImageDecoder.decodeBitmap( ImageDecoder.createSource(context.getResources(), R.drawable.fountain_night), (decoder, info, source1) -> { decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)); }); source.setGainmap(null); + assertEquals(source.getColorSpace().getId(), ColorSpace.Named.SRGB.ordinal()); + + while (state.keepRunning()) { + Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true) + .recycle(); + } + } + + @Test + public void testCreateScaledP3Bitmap() throws IOException { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final Context context = InstrumentationRegistry.getContext(); + Bitmap source = ImageDecoder.decodeBitmap( + ImageDecoder.createSource(context.getResources(), R.drawable.fountain_night), + (decoder, info, source1) -> { + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); + }); + source.setGainmap(null); + assertEquals(source.getColorSpace().getId(), ColorSpace.Named.DISPLAY_P3.ordinal()); while (state.keepRunning()) { Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true) diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt index 4352c8ae982e..ea10690bd672 100644 --- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt @@ -24,11 +24,11 @@ import android.content.pm.parsing.result.ParseTypeImpl import android.content.res.TypedArray import android.perftests.utils.BenchmarkState import android.perftests.utils.PerfStatusReporter -import android.util.ArraySet import androidx.test.filters.LargeTest import com.android.internal.pm.parsing.pkg.PackageImpl import com.android.internal.pm.pkg.parsing.ParsingPackageUtils import com.android.internal.util.ConcurrentUtils +import com.android.server.SystemConfig import java.io.File import java.io.FileOutputStream import java.util.concurrent.ArrayBlockingQueue @@ -217,8 +217,10 @@ public class PackageParsingPerfTest { isCoreApp, this, ) - override fun getHiddenApiWhitelistedApps() = ArraySet<String>() - override fun getInstallConstraintsAllowlist() = ArraySet<String>() + override fun getHiddenApiWhitelistedApps() = + SystemConfig.getInstance().hiddenApiWhitelistedApps + override fun getInstallConstraintsAllowlist() = + SystemConfig.getInstance().installConstraintsAllowlist }) override fun parseImpl(file: File) = diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 385fd509757b..14195c473c6d 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -241,6 +241,23 @@ public class StatusBarManager { public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3; /** + * Broadcast action: sent to apps that hold the status bar permission when + * KeyguardManager#setPrivateNotificationsAllowed() is changed. + * + * Extras: #EXTRA_KM_PRIVATE_NOTIFS_ALLOWED + * @hide + */ + public static final String ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED + = "android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED"; + + /** + * Boolean, the latest value of KeyguardManager#getPrivateNotificationsAllowed() + * @hide + */ + public static final String EXTRA_KM_PRIVATE_NOTIFS_ALLOWED + = "android.app.extra.KM_PRIVATE_NOTIFS_ALLOWED"; + + /** * Session flag for {@link #registerSessionListener} indicating the listener * is interested in sessions on the keygaurd. * Keyguard Session Boundaries: diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index fb0edb954539..d11c6c50da3b 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -36,3 +36,10 @@ flag { description: "Guards the security fix that ensures all URIs in intents and Person.java are valid" bug: "281044385" } + +flag { + name: "keyguard_private_notifications" + namespace: "systemui" + description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()" + bug: "309920145" +} diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java index 3ce094ef7467..cbb0ae784f82 100644 --- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java +++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.app.ResultInfo; +import android.content.Context; import android.content.res.CompatibilityInfo; import android.os.IBinder; import android.os.Parcel; @@ -85,6 +86,12 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { client.reportRelaunch(r); } + @Nullable + @Override + public Context getContextToUpdate(@NonNull ClientTransactionHandler client) { + return client.getActivity(getActivityToken()); + } + // ObjectPoolItem implementation private ActivityRelaunchItem() {} diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index d2ef65aec698..1190bf6a604b 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -24,12 +24,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityClient; import android.app.ActivityOptions; +import android.app.ActivityThread; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.app.IActivityClientController; import android.app.ProfilerInfo; import android.app.ResultInfo; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.CompatibilityInfo; @@ -115,6 +117,13 @@ public class LaunchActivityItem extends ClientTransactionItem { client.countLaunchingActivities(-1); } + @Nullable + @Override + public Context getContextToUpdate(@NonNull ClientTransactionHandler client) { + // LaunchActivityItem may update the global config with #mCurConfig. + return ActivityThread.currentApplication(); + } + // ObjectPoolItem implementation private LaunchActivityItem() {} diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java index 961da19daeed..1353d1679427 100644 --- a/core/java/android/app/servertransaction/MoveToDisplayItem.java +++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; +import android.content.Context; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.os.IBinder; @@ -55,6 +56,12 @@ public class MoveToDisplayItem extends ActivityTransactionItem { Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } + @Nullable + @Override + public Context getContextToUpdate(@NonNull ClientTransactionHandler client) { + return client.getActivity(getActivityToken()); + } + // ObjectPoolItem implementation private MoveToDisplayItem() {} diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index ee48e431ba8e..5dd4eb72c201 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -41,6 +41,7 @@ import android.content.Context; import android.content.res.Configuration; import android.os.IBinder; import android.os.Process; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; @@ -62,8 +63,11 @@ public class TransactionExecutor { private final PendingTransactionActions mPendingActions = new PendingTransactionActions(); private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper(); - /** Keeps track of display ids whose Configuration got updated within a transaction. */ - private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>(); + /** + * Keeps track of the Context whose Configuration got updated within a transaction, mapping to + * the config before the transaction. + */ + private final ArrayMap<Context, Configuration> mContextToPreChangedConfigMap = new ArrayMap<>(); /** Initialize an instance with transaction handler, that will execute all requested actions. */ public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) { @@ -91,16 +95,35 @@ public class TransactionExecutor { executeLifecycleState(transaction); } - if (!mConfigUpdatedDisplayIds.isEmpty()) { + if (!mContextToPreChangedConfigMap.isEmpty()) { // Whether this transaction should trigger DisplayListener#onDisplayChanged. - final ClientTransactionListenerController controller = - ClientTransactionListenerController.getInstance(); - final int displayCount = mConfigUpdatedDisplayIds.size(); - for (int i = 0; i < displayCount; i++) { - final int displayId = mConfigUpdatedDisplayIds.valueAt(i); - controller.onDisplayChanged(displayId); + try { + // Calculate display ids that have config changed. + final ArraySet<Integer> configUpdatedDisplayIds = new ArraySet<>(); + final int contextCount = mContextToPreChangedConfigMap.size(); + for (int i = 0; i < contextCount; i++) { + final Context context = mContextToPreChangedConfigMap.keyAt(i); + final Configuration preTransactionConfig = + mContextToPreChangedConfigMap.valueAt(i); + final Configuration postTransactionConfig = context.getResources() + .getConfiguration(); + if (!areConfigurationsEqualForDisplay( + postTransactionConfig, preTransactionConfig)) { + configUpdatedDisplayIds.add(context.getDisplayId()); + } + } + + // Dispatch the display changed callbacks. + final ClientTransactionListenerController controller = + ClientTransactionListenerController.getInstance(); + final int displayCount = configUpdatedDisplayIds.size(); + for (int i = 0; i < displayCount; i++) { + final int displayId = configUpdatedDisplayIds.valueAt(i); + controller.onDisplayChanged(displayId); + } + } finally { + mContextToPreChangedConfigMap.clear(); } - mConfigUpdatedDisplayIds.clear(); } mPendingActions.clear(); @@ -182,26 +205,24 @@ public class TransactionExecutor { } } - // Can't read flag from isolated process. - final boolean isBundleClientTransactionFlagEnabled = !Process.isIsolated() - && bundleClientTransactionFlag(); - final Context configUpdatedContext = isBundleClientTransactionFlagEnabled + final boolean shouldTrackConfigUpdatedContext = + // No configuration change for local transaction. + !mTransactionHandler.isExecutingLocalTransaction() + // Can't read flag from isolated process. + && !Process.isIsolated() + && bundleClientTransactionFlag(); + final Context configUpdatedContext = shouldTrackConfigUpdatedContext ? item.getContextToUpdate(mTransactionHandler) : null; - final Configuration preExecutedConfig = configUpdatedContext != null - ? new Configuration(configUpdatedContext.getResources().getConfiguration()) - : null; + if (configUpdatedContext != null + && !mContextToPreChangedConfigMap.containsKey(configUpdatedContext)) { + // Keep track of the first pre-executed config of each changed Context. + mContextToPreChangedConfigMap.put(configUpdatedContext, + new Configuration(configUpdatedContext.getResources().getConfiguration())); + } item.execute(mTransactionHandler, mPendingActions); - if (configUpdatedContext != null) { - final Configuration postExecutedConfig = configUpdatedContext.getResources() - .getConfiguration(); - if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) { - mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId()); - } - } - item.postExecute(mTransactionHandler, mPendingActions); if (r == null) { // Launch activity request will create an activity record. diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java index 7d3eb8783c04..193b03ced0b0 100644 --- a/core/java/android/app/servertransaction/WindowStateResizeItem.java +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -22,9 +22,12 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityThread; import android.app.ClientTransactionHandler; +import android.content.Context; import android.os.Parcel; import android.os.RemoteException; +import android.os.Trace; import android.util.MergedConfiguration; import android.view.IWindow; import android.view.InsetsState; @@ -52,6 +55,11 @@ public class WindowStateResizeItem extends ClientTransactionItem { @Override public void execute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, + mReportDraw ? "windowResizedReport" : "windowResized"); + if (mWindow instanceof ResizeListener listener) { + listener.onExecutingWindowStateResizeItem(); + } try { mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing); @@ -59,6 +67,14 @@ public class WindowStateResizeItem extends ClientTransactionItem { // Should be a local call. throw new RuntimeException(e); } + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + } + + @Nullable + @Override + public Context getContextToUpdate(@NonNull ClientTransactionHandler client) { + // WindowStateResizeItem may update the global config with #mConfiguration. + return ActivityThread.currentApplication(); } // ObjectPoolItem implementation @@ -80,7 +96,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { instance.mFrames = new ClientWindowFrames(frames); instance.mReportDraw = reportDraw; instance.mConfiguration = new MergedConfiguration(configuration); - instance.mInsetsState = new InsetsState(insetsState); + instance.mInsetsState = new InsetsState(insetsState, true /* copySources */); instance.mForceLayout = forceLayout; instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; instance.mDisplayId = displayId; @@ -190,4 +206,10 @@ public class WindowStateResizeItem extends ClientTransactionItem { + ", configuration=" + mConfiguration + "}"; } + + /** The interface for IWindow to perform resize directly if possible. */ + public interface ResizeListener { + /** Notifies that IWindow#resized is going to be called from WindowStateResizeItem. */ + void onExecutingWindowStateResizeItem(); + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b75c64dcc3c1..fa76e3976a58 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3547,6 +3547,8 @@ public abstract class Context { * * @param receiver The BroadcastReceiver to unregister. * + * @throws IllegalArgumentException if the {@code receiver} was not previously registered or + * already unregistered. * @see #registerReceiver */ public abstract void unregisterReceiver(BroadcastReceiver receiver); diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java index b69410cf7d73..a86396cd7c8d 100644 --- a/core/java/android/net/LocalSocket.java +++ b/core/java/android/net/LocalSocket.java @@ -196,7 +196,8 @@ public class LocalSocket implements Closeable { } /** - * Retrieves the input stream for this instance. + * Retrieves the input stream for this instance. Closing this stream is equivalent to closing + * the entire socket and its associated streams using {@link #close()}. * * @return input stream * @throws IOException if socket has been closed or cannot be created. @@ -207,7 +208,8 @@ public class LocalSocket implements Closeable { } /** - * Retrieves the output stream for this instance. + * Retrieves the output stream for this instance. Closing this stream is equivalent to closing + * the entire socket and its associated streams using {@link #close()}. * * @return output stream * @throws IOException if socket has been closed or cannot be created. diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 83d237d6e53b..d64614628b61 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -91,3 +91,10 @@ flag { is_fixed_read_only: true bug: "315037695" } + +flag { + name: "strict_mode_restricted_network" + namespace: "backstage_power" + description: "Guards StrictMode APIs for detecting restricted network access." + bug: "317250784" +} diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 78248d9775f4..e1965ef25562 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -53,7 +53,6 @@ import android.view.contentcapture.ContentCaptureSessionId; import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IContentCaptureDirectManager; -import android.view.contentcapture.MainContentCaptureSession; import com.android.internal.os.IResultReceiver; import com.android.internal.util.FrameworkStatsLog; @@ -724,7 +723,7 @@ public abstract class ContentCaptureService extends Service { final Bundle extras; if (binder != null) { extras = new Bundle(); - extras.putBinder(MainContentCaptureSession.EXTRA_BINDER, binder); + extras.putBinder(ContentCaptureSession.EXTRA_BINDER, binder); } else { extras = null; } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index f7b9aa26ad96..079991a81e77 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -76,7 +76,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "InsetsAsyncAnimation: " + WindowInsets.Type.toString(runner.getTypes()), runner.getTypes()); - releaseControls(mControl.getControls()); + InsetsController.releaseControls(mControl.getControls()); mMainThreadHandler.post(() -> mOuterCallbacks.notifyFinished(InsetsAnimationThreadControlRunner.this, shown)); } @@ -130,12 +130,6 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro }); } - private void releaseControls(SparseArray<InsetsSourceControl> controls) { - for (int i = controls.size() - 1; i >= 0; i--) { - controls.valueAt(i).release(SurfaceControl::release); - } - } - @Override @UiThread public void dumpDebug(ProtoOutputStream proto, long fieldId) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 147c15bea076..dd091571fbaf 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1353,6 +1353,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation }); } + // The leashes are copied, but they won't be used. + releaseControls(controls); + // The requested visibilities should be delayed as well. Otherwise, we might override // the insets visibility before playing animation. setRequestedVisibleTypes(mReportedRequestedVisibleTypes, types); @@ -1422,6 +1425,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } + static void releaseControls(SparseArray<InsetsSourceControl> controls) { + for (int i = controls.size() - 1; i >= 0; i--) { + controls.valueAt(i).release(SurfaceControl::release); + } + } + // TODO(b/242962223): Make this setter restrictive. @Override public void setSystemDrivenInsetsAnimationLoggingListener( diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ec994590e339..442ea661585c 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -10760,11 +10760,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } - session.internalNotifyViewTreeEvent(/* started= */ true); + session.notifyViewTreeEvent(/* started= */ true); try { dispatchProvideContentCaptureStructure(); } finally { - session.internalNotifyViewTreeEvent(/* started= */ false); + session.notifyViewTreeEvent(/* started= */ false); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1f81a6418962..32afe065857d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -111,6 +111,7 @@ import android.app.ICompatCameraControlCallback; import android.app.ResourcesManager; import android.app.WindowConfiguration; import android.app.compat.CompatChanges; +import android.app.servertransaction.WindowStateResizeItem; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ClipDescription; @@ -208,7 +209,6 @@ import android.view.animation.Interpolator; import android.view.autofill.AutofillManager; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureSession; -import android.view.contentcapture.MainContentCaptureSession; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; @@ -2012,26 +2012,24 @@ public final class ViewRootImpl implements ViewParent, } /** Handles messages {@link #MSG_RESIZED} and {@link #MSG_RESIZED_REPORT}. */ - private void handleResized(int msg, SomeArgs args) { + private void handleResized(ClientWindowFrames frames, boolean reportDraw, + MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, + boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { if (!mAdded) { return; } - final ClientWindowFrames frames = (ClientWindowFrames) args.arg1; - final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2; CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration); - final boolean forceNextWindowRelayout = args.argi1 != 0; - final int displayId = args.argi3; - final boolean dragResizing = args.argi5 != 0; - final Rect frame = frames.frame; final Rect displayFrame = frames.displayFrame; final Rect attachedFrame = frames.attachedFrame; if (mTranslator != null) { + mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); mTranslator.translateRectInScreenToAppWindow(frame); mTranslator.translateRectInScreenToAppWindow(displayFrame); mTranslator.translateRectInScreenToAppWindow(attachedFrame); } + mInsetsController.onStateChanged(insetsState); final float compatScale = frames.compatScale; final boolean frameChanged = !mWinFrame.equals(frame); final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration); @@ -2040,8 +2038,8 @@ public final class ViewRootImpl implements ViewParent, final boolean displayChanged = mDisplay.getDisplayId() != displayId; final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale; final boolean dragResizingChanged = mPendingDragResizing != dragResizing; - if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged - && !displayChanged && !forceNextWindowRelayout + if (!reportDraw && !frameChanged && !configChanged && !attachedFrameChanged + && !displayChanged && !forceLayout && !compatScaleChanged && !dragResizingChanged) { return; } @@ -2073,11 +2071,11 @@ public final class ViewRootImpl implements ViewParent, } } - mForceNextWindowRelayout |= forceNextWindowRelayout; - mPendingAlwaysConsumeSystemBars = args.argi2 != 0; - mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId; + mForceNextWindowRelayout |= forceLayout; + mPendingAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + mSyncSeqId = syncSeqId > mSyncSeqId ? syncSeqId : mSyncSeqId; - if (msg == MSG_RESIZED_REPORT) { + if (reportDraw) { reportNextDraw("resized"); } @@ -4104,7 +4102,7 @@ public final class ViewRootImpl implements ViewParent, final ContentCaptureManager manager = mAttachInfo.mContentCaptureManager; if (manager != null && mAttachInfo.mContentCaptureEvents != null) { - final MainContentCaptureSession session = manager.getMainContentCaptureSession(); + final ContentCaptureSession session = manager.getMainContentCaptureSession(); session.notifyContentCaptureEvents(mAttachInfo.mContentCaptureEvents); } mAttachInfo.mContentCaptureEvents = null; @@ -5021,7 +5019,7 @@ public final class ViewRootImpl implements ViewParent, // Initial dispatch of window bounds to content capture if (mAttachInfo.mContentCaptureManager != null) { - MainContentCaptureSession session = + ContentCaptureSession session = mAttachInfo.mContentCaptureManager.getMainContentCaptureSession(); session.notifyWindowBoundsChanged(session.getId(), getConfiguration().windowConfiguration.getBounds()); @@ -6232,8 +6230,17 @@ public final class ViewRootImpl implements ViewParent, case MSG_RESIZED: case MSG_RESIZED_REPORT: { final SomeArgs args = (SomeArgs) msg.obj; - mInsetsController.onStateChanged((InsetsState) args.arg3); - handleResized(msg.what, args); + final ClientWindowFrames frames = (ClientWindowFrames) args.arg1; + final boolean reportDraw = msg.what == MSG_RESIZED_REPORT; + final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2; + final InsetsState insetsState = (InsetsState) args.arg3; + final boolean forceLayout = args.argi1 != 0; + final boolean alwaysConsumeSystemBars = args.argi2 != 0; + final int displayId = args.argi3; + final int syncSeqId = args.argi4; + final boolean dragResizing = args.argi5 != 0; + handleResized(frames, reportDraw, mergedConfiguration, insetsState, forceLayout, + alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing); args.recycle(); break; } @@ -8797,7 +8804,7 @@ public final class ViewRootImpl implements ViewParent, mSurfaceControl.setTransformHint(transformHint); if (mAttachInfo.mContentCaptureManager != null) { - MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager + ContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager .getMainContentCaptureSession(); mainSession.notifyWindowBoundsChanged(mainSession.getId(), getConfiguration().windowConfiguration.getBounds()); @@ -9379,20 +9386,8 @@ public final class ViewRootImpl implements ViewParent, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED); SomeArgs args = SomeArgs.obtain(); - final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid()); - if (sameProcessCall) { - insetsState = new InsetsState(insetsState, true /* copySource */); - } - if (mTranslator != null) { - mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); - } - if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) { - ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchResized", - getInsetsController().getHost().getInputMethodManager(), null /* icProto */); - } - args.arg1 = sameProcessCall ? new ClientWindowFrames(frames) : frames; - args.arg2 = sameProcessCall && mergedConfiguration != null - ? new MergedConfiguration(mergedConfiguration) : mergedConfiguration; + args.arg1 = frames; + args.arg2 = mergedConfiguration; args.arg3 = insetsState; args.argi1 = forceLayout ? 1 : 0; args.argi2 = alwaysConsumeSystemBars ? 1 : 0; @@ -10815,9 +10810,10 @@ public final class ViewRootImpl implements ViewParent, } } - static class W extends IWindow.Stub { + static class W extends IWindow.Stub implements WindowStateResizeItem.ResizeListener { private final WeakReference<ViewRootImpl> mViewAncestor; private final IWindowSession mWindowSession; + private boolean mIsFromResizeItem; W(ViewRootImpl viewAncestor) { mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor); @@ -10825,17 +10821,46 @@ public final class ViewRootImpl implements ViewParent, } @Override + public void onExecutingWindowStateResizeItem() { + mIsFromResizeItem = true; + } + + @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { + final boolean isFromResizeItem = mIsFromResizeItem; + mIsFromResizeItem = false; // Although this is a AIDL method, it will only be triggered in local process through // either WindowStateResizeItem or WindowlessWindowManager. final ViewRootImpl viewAncestor = mViewAncestor.get(); - if (viewAncestor != null) { - viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState, + if (viewAncestor == null) { + return; + } + if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) { + ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#resized", + viewAncestor.getInsetsController().getHost().getInputMethodManager(), + null /* icProto */); + } + // If the UI thread is the same as the current thread that is dispatching + // WindowStateResizeItem, then it can run directly. + if (isFromResizeItem && viewAncestor.mHandler.getLooper() + == ActivityThread.currentActivityThread().getLooper()) { + viewAncestor.handleResized(frames, reportDraw, mergedConfiguration, insetsState, forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing); + return; + } + // The the parameters from WindowStateResizeItem are already copied. + final boolean needCopy = + !isFromResizeItem && (Binder.getCallingPid() == Process.myPid()); + if (needCopy) { + insetsState = new InsetsState(insetsState, true /* copySource */); + frames = new ClientWindowFrames(frames); + mergedConfiguration = new MergedConfiguration(mergedConfiguration); } + viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState, + forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing); } @Override @@ -11910,6 +11935,12 @@ public final class ViewRootImpl implements ViewParent, if (syncBuffer) { boolean result = mBlastBufferQueue.syncNextTransaction(transaction -> { + Runnable timeoutRunnable = () -> Log.e(mTag, + "Failed to submit the sync transaction after 4s. Likely to ANR " + + "soon"); + mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER); + transaction.addTransactionCommittedListener(mSimpleExecutor, + () -> mHandler.removeCallbacks(timeoutRunnable)); surfaceSyncGroup.addTransaction(transaction); surfaceSyncGroup.markSyncReady(); }); diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java index 44b4353871a2..70c899f1efc7 100644 --- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java @@ -17,10 +17,16 @@ package android.view.contentcapture; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ComponentName; import android.graphics.Insets; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.SparseArray; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; +import java.util.ArrayList; + /** * A session that is explicitly created by the app (and hence is a descendant of * {@link MainContentCaptureSession}). @@ -40,17 +46,30 @@ final class ChildContentCaptureSession extends ContentCaptureSession { } @Override - MainContentCaptureSession getMainCaptureSession() { - if (mParent instanceof MainContentCaptureSession) { - return (MainContentCaptureSession) mParent; - } + ContentCaptureSession getMainCaptureSession() { return mParent.getMainCaptureSession(); } @Override + void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, + @NonNull ComponentName component, int flags) { + getMainCaptureSession().start(token, shareableActivityToken, component, flags); + } + + @Override + boolean isDisabled() { + return getMainCaptureSession().isDisabled(); + } + + @Override + boolean setDisabled(boolean disabled) { + return getMainCaptureSession().setDisabled(disabled); + } + + @Override ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) { final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext); - getMainCaptureSession().notifyChildSessionStarted(mId, child.mId, clientContext); + internalNotifyChildSessionStarted(mId, child.mId, clientContext); return child; } @@ -61,51 +80,80 @@ final class ChildContentCaptureSession extends ContentCaptureSession { @Override public void updateContentCaptureContext(@Nullable ContentCaptureContext context) { - getMainCaptureSession().notifyContextUpdated(mId, context); + internalNotifyContextUpdated(mId, context); } @Override void onDestroy() { - getMainCaptureSession().notifyChildSessionFinished(mParent.mId, mId); + internalNotifyChildSessionFinished(mParent.mId, mId); + } + + @Override + void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, + @NonNull ContentCaptureContext clientContext) { + getMainCaptureSession() + .internalNotifyChildSessionStarted(parentSessionId, childSessionId, clientContext); + } + + @Override + void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) { + getMainCaptureSession().internalNotifyChildSessionFinished(parentSessionId, childSessionId); + } + + @Override + void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { + getMainCaptureSession().internalNotifyContextUpdated(sessionId, context); } @Override - void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) { - getMainCaptureSession().notifyViewAppeared(mId, node); + void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { + getMainCaptureSession().internalNotifyViewAppeared(sessionId, node); } @Override - void internalNotifyViewDisappeared(@NonNull AutofillId id) { - getMainCaptureSession().notifyViewDisappeared(mId, id); + void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) { + getMainCaptureSession().internalNotifyViewDisappeared(sessionId, id); } @Override - void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) { - getMainCaptureSession().notifyViewTextChanged(mId, id, text); + void internalNotifyViewTextChanged( + int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { + getMainCaptureSession().internalNotifyViewTextChanged(sessionId, id, text); } @Override - void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) { - getMainCaptureSession().notifyViewInsetsChanged(mId, viewInsets); + void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { + getMainCaptureSession().internalNotifyViewInsetsChanged(mId, viewInsets); } @Override - public void internalNotifyViewTreeEvent(boolean started) { - getMainCaptureSession().notifyViewTreeEvent(mId, started); + public void internalNotifyViewTreeEvent(int sessionId, boolean started) { + getMainCaptureSession().internalNotifyViewTreeEvent(sessionId, started); } @Override void internalNotifySessionResumed() { - getMainCaptureSession().notifySessionResumed(); + getMainCaptureSession().internalNotifySessionResumed(); } @Override void internalNotifySessionPaused() { - getMainCaptureSession().notifySessionPaused(); + getMainCaptureSession().internalNotifySessionPaused(); } @Override boolean isContentCaptureEnabled() { return getMainCaptureSession().isContentCaptureEnabled(); } + + @Override + public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { + getMainCaptureSession().notifyWindowBoundsChanged(sessionId, bounds); + } + + @Override + public void notifyContentCaptureEvents( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { + getMainCaptureSession().notifyContentCaptureEvents(contentCaptureEvents); + } } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index a8297472445f..bcef37f6e0c4 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -499,10 +499,14 @@ public final class ContentCaptureManager { @Nullable @GuardedBy("mLock") - private Handler mHandler; + private Handler mUiHandler; + + @Nullable + @GuardedBy("mLock") + private Handler mContentCaptureHandler; @GuardedBy("mLock") - private MainContentCaptureSession mMainSession; + private ContentCaptureSession mMainSession; @Nullable // set on-demand by addDumpable() private Dumper mDumpable; @@ -587,11 +591,10 @@ public final class ContentCaptureManager { */ @NonNull @UiThread - public MainContentCaptureSession getMainContentCaptureSession() { + public ContentCaptureSession getMainContentCaptureSession() { synchronized (mLock) { if (mMainSession == null) { - mMainSession = new MainContentCaptureSession( - mContext, this, prepareContentCaptureHandler(), mService); + mMainSession = prepareMainSession(); if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); } return mMainSession; @@ -600,15 +603,36 @@ public final class ContentCaptureManager { @NonNull @GuardedBy("mLock") + private ContentCaptureSession prepareMainSession() { + if (runOnBackgroundThreadEnabled()) { + return new MainContentCaptureSessionV2( + mContext, + this, + prepareUiHandler(), + prepareContentCaptureHandler(), + mService + ); + } else { + return new MainContentCaptureSession(mContext, this, prepareUiHandler(), mService); + } + } + + @NonNull + @GuardedBy("mLock") private Handler prepareContentCaptureHandler() { - if (mHandler == null) { - if (runOnBackgroundThreadEnabled()) { - mHandler = BackgroundThread.getHandler(); - } else { - mHandler = Handler.createAsync(Looper.getMainLooper()); - } + if (mContentCaptureHandler == null) { + mContentCaptureHandler = BackgroundThread.getHandler(); + } + return mContentCaptureHandler; + } + + @NonNull + @GuardedBy("mLock") + private Handler prepareUiHandler() { + if (mUiHandler == null) { + mUiHandler = Handler.createAsync(Looper.getMainLooper()); } - return mHandler; + return mUiHandler; } /** @hide */ @@ -726,7 +750,7 @@ public final class ContentCaptureManager { public boolean isContentCaptureEnabled() { if (mOptions.lite) return false; - final MainContentCaptureSession mainSession; + final ContentCaptureSession mainSession; synchronized (mLock) { mainSession = mMainSession; } @@ -777,7 +801,7 @@ public final class ContentCaptureManager { Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext); } - MainContentCaptureSession mainSession; + ContentCaptureSession mainSession; synchronized (mLock) { if (enabled) { mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP; @@ -803,7 +827,7 @@ public final class ContentCaptureManager { final boolean flagSecureEnabled = (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0; - MainContentCaptureSession mainSession; + ContentCaptureSession mainSession; boolean alreadyDisabledByApp; synchronized (mLock) { alreadyDisabledByApp = (mFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0; diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index bb815c0e8317..0ca36ba28e3a 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -27,9 +27,13 @@ import android.annotation.Nullable; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; +import android.content.ComponentName; import android.graphics.Insets; +import android.graphics.Rect; +import android.os.IBinder; import android.util.DebugUtils; import android.util.Log; +import android.util.SparseArray; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; @@ -37,6 +41,7 @@ import android.view.contentcapture.ViewNode.ViewStructureImpl; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.IResultReceiver; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; @@ -60,6 +65,18 @@ public abstract class ContentCaptureSession implements AutoCloseable { private static final SecureRandom ID_GENERATOR = new SecureRandom(); /** + * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service. + * @hide + */ + public static final String EXTRA_BINDER = "binder"; + + /** + * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state. + * @hide + */ + public static final String EXTRA_ENABLED_STATE = "enabled"; + + /** * Initial state, when there is no session. * * @hide @@ -262,7 +279,19 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** @hide */ @NonNull - abstract MainContentCaptureSession getMainCaptureSession(); + abstract ContentCaptureSession getMainCaptureSession(); + + abstract void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, + @NonNull ComponentName component, int flags); + + abstract boolean isDisabled(); + + /** + * Sets the disabled state of content capture. + * + * @return whether disabled state was changed. + */ + abstract boolean setDisabled(boolean disabled); /** * Gets the id used to identify this session. @@ -400,10 +429,11 @@ public abstract class ContentCaptureSession implements AutoCloseable { throw new IllegalArgumentException("Invalid node class: " + node.getClass()); } - internalNotifyViewAppeared((ViewStructureImpl) node); + internalNotifyViewAppeared(mId, (ViewStructureImpl) node); } - abstract void internalNotifyViewAppeared(@NonNull ViewNode.ViewStructureImpl node); + abstract void internalNotifyViewAppeared( + int sessionId, @NonNull ViewNode.ViewStructureImpl node); /** * Notifies the Content Capture Service that a node has been removed from the view structure. @@ -420,10 +450,10 @@ public abstract class ContentCaptureSession implements AutoCloseable { Objects.requireNonNull(id); if (!isContentCaptureEnabled()) return; - internalNotifyViewDisappeared(id); + internalNotifyViewDisappeared(mId, id); } - abstract void internalNotifyViewDisappeared(@NonNull AutofillId id); + abstract void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id); /** * Notifies the Content Capture Service that a list of nodes has appeared in the view structure. @@ -445,12 +475,12 @@ public abstract class ContentCaptureSession implements AutoCloseable { } } - internalNotifyViewTreeEvent(/* started= */ true); + internalNotifyViewTreeEvent(mId, /* started= */ true); for (int i = 0; i < appearedNodes.size(); i++) { ViewStructure v = appearedNodes.get(i); - internalNotifyViewAppeared((ViewStructureImpl) v); + internalNotifyViewAppeared(mId, (ViewStructureImpl) v); } - internalNotifyViewTreeEvent(/* started= */ false); + internalNotifyViewTreeEvent(mId, /* started= */ false); } /** @@ -476,15 +506,15 @@ public abstract class ContentCaptureSession implements AutoCloseable { if (!isContentCaptureEnabled()) return; if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) { - internalNotifyViewTreeEvent(/* started= */ true); + internalNotifyViewTreeEvent(mId, /* started= */ true); } // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is // parcelized for (long id : virtualIds) { - internalNotifyViewDisappeared(new AutofillId(hostId, id, mId)); + internalNotifyViewDisappeared(mId, new AutofillId(hostId, id, mId)); } if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) { - internalNotifyViewTreeEvent(/* started= */ false); + internalNotifyViewTreeEvent(mId, /* started= */ false); } } @@ -499,10 +529,10 @@ public abstract class ContentCaptureSession implements AutoCloseable { if (!isContentCaptureEnabled()) return; - internalNotifyViewTextChanged(id, text); + internalNotifyViewTextChanged(mId, id, text); } - abstract void internalNotifyViewTextChanged(@NonNull AutofillId id, + abstract void internalNotifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text); /** @@ -513,13 +543,18 @@ public abstract class ContentCaptureSession implements AutoCloseable { if (!isContentCaptureEnabled()) return; - internalNotifyViewInsetsChanged(viewInsets); + internalNotifyViewInsetsChanged(mId, viewInsets); } - abstract void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets); + abstract void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets); + + /** @hide */ + public void notifyViewTreeEvent(boolean started) { + internalNotifyViewTreeEvent(mId, started); + } /** @hide */ - public abstract void internalNotifyViewTreeEvent(boolean started); + abstract void internalNotifyViewTreeEvent(int sessionId, boolean started); /** * Notifies the Content Capture Service that a session has resumed. @@ -543,6 +578,21 @@ public abstract class ContentCaptureSession implements AutoCloseable { abstract void internalNotifySessionPaused(); + abstract void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, + @NonNull ContentCaptureContext clientContext); + + abstract void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId); + + abstract void internalNotifyContextUpdated( + int sessionId, @Nullable ContentCaptureContext context); + + /** @hide */ + public abstract void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds); + + /** @hide */ + public abstract void notifyContentCaptureEvents( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents); + /** * Creates a {@link ViewStructure} for a "standard" view. * diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 19ba316257a3..a90c94eac967 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -31,7 +31,6 @@ import static android.view.contentcapture.ContentCaptureHelper.getSanitizedStrin import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; -import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled; import android.annotation.NonNull; import android.annotation.Nullable; @@ -70,10 +69,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +// TODO(b/309411951): Replace V2 as the only main session once the experiment is done. /** * Main session associated with a context. * @@ -82,6 +81,7 @@ import java.util.concurrent.atomic.AtomicInteger; * * @hide */ +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class MainContentCaptureSession extends ContentCaptureSession { private static final String TAG = MainContentCaptureSession.class.getSimpleName(); @@ -97,18 +97,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession { */ private static final int MSG_FLUSH = 1; - /** - * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service. - * @hide - */ - public static final String EXTRA_BINDER = "binder"; - - /** - * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state. - * @hide - */ - public static final String EXTRA_ENABLED_STATE = "enabled"; - @NonNull private final AtomicBoolean mDisabled = new AtomicBoolean(false); @@ -154,15 +142,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession { public ComponentName mComponentName; /** - * Thread-safe queue of events held to be processed as a batch. - * - * Because it is not guaranteed that the events will be enqueued from a single thread, the - * implementation must be thread-safe to prevent unexpected behaviour. - */ - @NonNull - private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue; - - /** * List of events held to be sent to the {@link ContentCaptureService} as a batch. * * @hide @@ -221,14 +200,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { binder = resultData.getBinder(EXTRA_BINDER); if (binder == null) { Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); - mainSession.runOnContentCaptureThread(() -> mainSession.resetSession( + mainSession.mHandler.post(() -> mainSession.resetSession( STATE_DISABLED | STATE_INTERNAL_ERROR)); return; } } else { binder = null; } - mainSession.runOnContentCaptureThread(() -> + mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder)); } } @@ -249,39 +228,27 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null; mSessionStateReceiver = new SessionStateReceiver(this); - - mEventProcessQueue = new ConcurrentLinkedQueue<>(); } @Override - MainContentCaptureSession getMainCaptureSession() { + ContentCaptureSession getMainCaptureSession() { return this; } @Override ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) { final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext); - notifyChildSessionStarted(mId, child.mId, clientContext); + internalNotifyChildSessionStarted(mId, child.mId, clientContext); return child; } /** * Starts this session. */ + @Override void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { - if (runOnBackgroundThreadEnabled()) { - runOnContentCaptureThread( - () -> startImpl(token, shareableActivityToken, component, flags)); - } else { - // Preserve the control arm behaviour. - startImpl(token, shareableActivityToken, component, flags); - } - } - - private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, - @NonNull ComponentName component, int flags) { - checkOnContentCaptureThread(); + checkOnUiThread(); if (!isContentCaptureEnabled()) return; if (sVerbose) { @@ -315,15 +282,17 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); } } + @Override void onDestroy() { - clearAndRunOnContentCaptureThread(() -> { + mHandler.removeMessages(MSG_FLUSH); + mHandler.post(() -> { try { flush(FLUSH_REASON_SESSION_FINISHED); } finally { destroySession(); } - }, MSG_FLUSH); + }); } /** @@ -336,7 +305,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void onSessionStarted(int resultCode, @Nullable IBinder binder) { - checkOnContentCaptureThread(); + checkOnUiThread(); if (binder != null) { mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); mDirectServiceVulture = () -> { @@ -385,7 +354,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { - checkOnContentCaptureThread(); + checkOnUiThread(); final int eventType = event.getType(); if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED @@ -429,14 +398,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { - checkOnContentCaptureThread(); + checkOnUiThread(); if (mContentProtectionEventProcessor != null) { mContentProtectionEventProcessor.processEvent(event); } } private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { - checkOnContentCaptureThread(); + checkOnUiThread(); final int eventType = event.getType(); final int maxBufferSize = mManager.mOptions.maxBufferSize; if (mEvents == null) { @@ -571,12 +540,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private boolean hasStarted() { - checkOnContentCaptureThread(); + checkOnUiThread(); return mState != UNKNOWN_STATE; } private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { - checkOnContentCaptureThread(); + checkOnUiThread(); if (sVerbose) { Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) + ", checkExisting=" + checkExisting); @@ -617,11 +586,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { + flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush)); } // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage() - mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); + mHandler.postDelayed(() -> + flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); } private void flushIfNeeded(@FlushReason int reason) { - checkOnContentCaptureThread(); + checkOnUiThread(); if (mEvents == null || mEvents.isEmpty()) { if (sVerbose) Log.v(TAG, "Nothing to flush"); return; @@ -633,16 +603,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override public void flush(@FlushReason int reason) { - if (runOnBackgroundThreadEnabled()) { - runOnContentCaptureThread(() -> flushImpl(reason)); - } else { - // Preserve the control arm behaviour. - flushImpl(reason); - } - } - - private void flushImpl(@FlushReason int reason) { - checkOnContentCaptureThread(); + checkOnUiThread(); if (mEvents == null || mEvents.size() == 0) { if (sVerbose) { Log.v(TAG, "Don't flush for empty event buffer."); @@ -703,7 +664,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @Override public void updateContentCaptureContext(@Nullable ContentCaptureContext context) { - notifyContextUpdated(mId, context); + internalNotifyContextUpdated(mId, context); } /** @@ -711,7 +672,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { */ @NonNull private ParceledListSlice<ContentCaptureEvent> clearEvents() { - checkOnContentCaptureThread(); + checkOnUiThread(); // NOTE: we must save a reference to the current mEvents and then set it to to null, // otherwise clearing it would clear it in the receiving side if the service is also local. if (mEvents == null) { @@ -726,7 +687,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void destroySession() { - checkOnContentCaptureThread(); + checkOnUiThread(); if (sDebug) { Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " @@ -746,9 +707,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } mDirectServiceInterface = null; mContentProtectionEventProcessor = null; - if (runOnBackgroundThreadEnabled()) { - mEventProcessQueue.clear(); - } } // TODO(b/122454205): once we support multiple sessions, we might need to move some of these @@ -756,7 +714,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void resetSession(int newState) { - checkOnContentCaptureThread(); + checkOnUiThread(); if (sVerbose) { Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " + getStateAsString(mState) + " to " + getStateAsString(newState)); @@ -781,91 +739,22 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } @Override - void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) { - notifyViewAppeared(mId, node); - } - - @Override - void internalNotifyViewDisappeared(@NonNull AutofillId id) { - notifyViewDisappeared(mId, id); - } - - @Override - void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) { - notifyViewTextChanged(mId, id, text); - } - - @Override - void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) { - notifyViewInsetsChanged(mId, viewInsets); - } - - @Override - public void internalNotifyViewTreeEvent(boolean started) { - notifyViewTreeEvent(mId, started); - } - - @Override - public void internalNotifySessionResumed() { - notifySessionResumed(mId); - } - - @Override - public void internalNotifySessionPaused() { - notifySessionPaused(mId); - } - - @Override - boolean isContentCaptureEnabled() { - return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled(); - } - - // Called by ContentCaptureManager.isContentCaptureEnabled - boolean isDisabled() { - return mDisabled.get(); - } - - /** - * Sets the disabled state of content capture. - * - * @return whether disabled state was changed. - */ - boolean setDisabled(boolean disabled) { - return mDisabled.compareAndSet(!disabled, disabled); - } - - // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is - // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such - // change should also get get rid of the "internalNotifyXXXX" methods above - void notifyChildSessionStarted(int parentSessionId, int childSessionId, - @NonNull ContentCaptureContext clientContext) { - final ContentCaptureEvent event = - new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) - .setParentSessionId(parentSessionId) - .setClientContext(clientContext); - enqueueEvent(event, FORCE_FLUSH); - } - - void notifyChildSessionFinished(int parentSessionId, int childSessionId) { - final ContentCaptureEvent event = - new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) - .setParentSessionId(parentSessionId); - enqueueEvent(event, FORCE_FLUSH); - } - - void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { + void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) .setViewNode(node.mNode); - enqueueEvent(event); + mHandler.post(() -> sendEvent(event)); } - void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { + @Override + void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) { final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED) .setAutofillId(id); - enqueueEvent(event); + mHandler.post(() -> sendEvent(event)); } - void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { + @Override + void internalNotifyViewTextChanged( + int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { // Since the same CharSequence instance may be reused in the TextView, we need to make // a copy of its content so that its value will not be changed by subsequent updates // in the TextView. @@ -891,113 +780,108 @@ public final class MainContentCaptureSession extends ContentCaptureSession { .setAutofillId(id).setText(eventText) .setComposingIndex(composingStart, composingEnd) .setSelectionIndex(startIndex, endIndex); - enqueueEvent(event); + mHandler.post(() -> sendEvent(event)); } - void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { + @Override + void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) .setInsets(viewInsets); - enqueueEvent(event); + mHandler.post(() -> sendEvent(event)); } - void notifyViewTreeEvent(int sessionId, boolean started) { + @Override + public void internalNotifyViewTreeEvent(int sessionId, boolean started) { final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled(); final boolean forceFlush = disableFlush ? !started : FORCE_FLUSH; final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type); - enqueueEvent(event, forceFlush); + mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); } - void notifySessionResumed(int sessionId) { - final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED); - enqueueEvent(event, FORCE_FLUSH); + @Override + public void internalNotifySessionResumed() { + final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_RESUMED); + mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); } - void notifySessionPaused(int sessionId) { - final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED); - enqueueEvent(event, FORCE_FLUSH); + @Override + public void internalNotifySessionPaused() { + final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_PAUSED); + mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); } - void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { - final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) - .setClientContext(context); - enqueueEvent(event, FORCE_FLUSH); + @Override + boolean isContentCaptureEnabled() { + return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled(); } - /** public because is also used by ViewRootImpl */ - public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { + @Override + boolean isDisabled() { + return mDisabled.get(); + } + + @Override + boolean setDisabled(boolean disabled) { + return mDisabled.compareAndSet(!disabled, disabled); + } + + @Override + void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, + @NonNull ContentCaptureContext clientContext) { final ContentCaptureEvent event = - new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) - .setBounds(bounds); - enqueueEvent(event); + new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) + .setParentSessionId(parentSessionId) + .setClientContext(clientContext); + mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); } - private List<ContentCaptureEvent> clearBufferEvents() { - final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>(); - ContentCaptureEvent event; - while ((event = mEventProcessQueue.poll()) != null) { - bufferEvents.add(event); - } - return bufferEvents; + @Override + void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) { + final ContentCaptureEvent event = + new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) + .setParentSessionId(parentSessionId); + mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); } - private void enqueueEvent(@NonNull final ContentCaptureEvent event) { - enqueueEvent(event, /* forceFlush */ false); + @Override + void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { + final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) + .setClientContext(context); + mHandler.post(() -> sendEvent(event, FORCE_FLUSH)); } - /** - * Enqueue the event into {@code mEventProcessBuffer} if it is not an urgent request. Otherwise, - * clear the buffer events then starting sending out current event. - */ - private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) { - if (runOnBackgroundThreadEnabled()) { - if (forceFlush) { - // The buffer events are cleared in the same thread first to prevent new events - // being added during the time of context switch. This would disrupt the sequence - // of events. - final List<ContentCaptureEvent> batchEvents = clearBufferEvents(); - runOnContentCaptureThread(() -> { - for (int i = 0; i < batchEvents.size(); i++) { - sendEvent(batchEvents.get(i)); - } - sendEvent(event, /* forceFlush= */ true); - }); - } else { - mEventProcessQueue.offer(event); - } - } else { - mHandler.post(() -> sendEvent(event, forceFlush)); - } + @Override + public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { + final ContentCaptureEvent event = + new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) + .setBounds(bounds); + mHandler.post(() -> sendEvent(event)); } - /** public because is also used by ViewRootImpl */ + @Override public void notifyContentCaptureEvents( @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { - if (runOnBackgroundThreadEnabled()) { - runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents)); - } else { - // Preserve the control arm behaviour. - notifyContentCaptureEventsImpl(contentCaptureEvents); - } + notifyContentCaptureEventsImpl(contentCaptureEvents); } private void notifyContentCaptureEventsImpl( @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { - checkOnContentCaptureThread(); + checkOnUiThread(); try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); } for (int i = 0; i < contentCaptureEvents.size(); i++) { int sessionId = contentCaptureEvents.keyAt(i); - notifyViewTreeEvent(sessionId, /* started= */ true); + internalNotifyViewTreeEvent(sessionId, /* started= */ true); ArrayList<Object> events = contentCaptureEvents.valueAt(i); for_each_event: for (int j = 0; j < events.size(); j++) { Object event = events.get(j); if (event instanceof AutofillId) { - notifyViewDisappeared(sessionId, (AutofillId) event); + internalNotifyViewDisappeared(sessionId, (AutofillId) event); } else if (event instanceof View) { View view = (View) event; ContentCaptureSession session = view.getContentCaptureSession(); @@ -1015,12 +899,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { view.onProvideContentCaptureStructure(structure, /* flags= */ 0); session.notifyViewAppeared(structure); } else if (event instanceof Insets) { - notifyViewInsetsChanged(sessionId, (Insets) event); + internalNotifyViewInsetsChanged(sessionId, (Insets) event); } else { Log.w(TAG, "invalid content capture event: " + event); } } - notifyViewTreeEvent(sessionId, /* started= */ false); + internalNotifyViewTreeEvent(sessionId, /* started= */ false); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); @@ -1131,9 +1015,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * Therefore, accessing internal properties in {@link MainContentCaptureSession} should * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> */ - private void checkOnContentCaptureThread() { - final boolean onContentCaptureThread = mHandler.getLooper().isCurrentThread(); - if (!onContentCaptureThread) { + private void checkOnUiThread() { + final boolean onUiThread = mHandler.getLooper().isCurrentThread(); + if (!onUiThread) { mWrongThreadCount.incrementAndGet(); Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread()); } @@ -1144,38 +1028,4 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Counter.logIncrement( CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID, mWrongThreadCount.getAndSet(0)); } - - /** - * Ensures that {@code r} will be running on the assigned thread. - * - * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable. - * </p> - */ - private void runOnContentCaptureThread(@NonNull Runnable r) { - if (runOnBackgroundThreadEnabled()) { - if (!mHandler.getLooper().isCurrentThread()) { - mHandler.post(r); - } else { - r.run(); - } - } else { - // Preserve the control arm behaviour to always post to the handler. - mHandler.post(r); - } - } - - private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { - if (runOnBackgroundThreadEnabled()) { - if (!mHandler.getLooper().isCurrentThread()) { - mHandler.removeMessages(what); - mHandler.post(r); - } else { - r.run(); - } - } else { - // Preserve the control arm behaviour to always post to the handler. - mHandler.removeMessages(what); - mHandler.post(r); - } - } } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java new file mode 100644 index 000000000000..bf1d31c8496d --- /dev/null +++ b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java @@ -0,0 +1,1184 @@ +/* + * Copyright (C) 2018 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 android.view.contentcapture; + +import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_INSETS_CHANGED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_WINDOW_BOUNDS_CHANGED; +import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; +import static android.view.contentcapture.ContentCaptureHelper.sDebug; +import static android.view.contentcapture.ContentCaptureHelper.sVerbose; +import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.pm.ParceledListSlice; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.os.Trace; +import android.service.contentcapture.ContentCaptureService; +import android.text.Selection; +import android.text.Spannable; +import android.text.TextUtils; +import android.util.LocalLog; +import android.util.Log; +import android.util.SparseArray; +import android.util.TimeUtils; +import android.view.View; +import android.view.ViewStructure; +import android.view.autofill.AutofillId; +import android.view.contentcapture.ViewNode.ViewStructureImpl; +import android.view.contentprotection.ContentProtectionEventProcessor; +import android.view.inputmethod.BaseInputConnection; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.IResultReceiver; +import com.android.modules.expresslog.Counter; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Main session associated with a context. + * + * <p>This is forked from {@link MainContentCaptureSession} to hold the logic of running operations + * in the background thread.</p> + * + * @hide + */ +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +public final class MainContentCaptureSessionV2 extends ContentCaptureSession { + + private static final String TAG = MainContentCaptureSession.class.getSimpleName(); + + private static final String CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID = + "content_capture.value_content_capture_wrong_thread_count"; + + // For readability purposes... + private static final boolean FORCE_FLUSH = true; + + /** + * Handler message used to flush the buffer. + */ + private static final int MSG_FLUSH = 1; + + @NonNull + private final AtomicBoolean mDisabled = new AtomicBoolean(false); + + @NonNull + private final ContentCaptureManager.StrippedContext mContext; + + @NonNull + private final ContentCaptureManager mManager; + + @NonNull + private final Handler mUiHandler; + + @NonNull + private final Handler mContentCaptureHandler; + + /** + * Interface to the system_server binder object - it's only used to start the session (and + * notify when the session is finished). + */ + @NonNull + private final IContentCaptureManager mSystemServerInterface; + + /** + * Direct interface to the service binder object - it's used to send the events, including the + * last ones (when the session is finished) + * + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @Nullable + public IContentCaptureDirectManager mDirectServiceInterface; + + @Nullable + private DeathRecipient mDirectServiceVulture; + + private int mState = UNKNOWN_STATE; + + @Nullable + private IBinder mApplicationToken; + @Nullable + private IBinder mShareableActivityToken; + + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @Nullable + public ComponentName mComponentName; + + /** + * Thread-safe queue of events held to be processed as a batch. + * + * Because it is not guaranteed that the events will be enqueued from a single thread, the + * implementation must be thread-safe to prevent unexpected behaviour. + */ + @NonNull + private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue; + + /** + * List of events held to be sent to the {@link ContentCaptureService} as a batch. + * + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @Nullable + public ArrayList<ContentCaptureEvent> mEvents; + + // Used just for debugging purposes (on dump) + private long mNextFlush; + + /** + * Whether the next buffer flush is queued by a text changed event. + */ + private boolean mNextFlushForTextChanged = false; + + @Nullable + private final LocalLog mFlushHistory; + + private final AtomicInteger mWrongThreadCount = new AtomicInteger(0); + + /** + * Binder object used to update the session state. + */ + @NonNull + private final SessionStateReceiver mSessionStateReceiver; + + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @Nullable + public ContentProtectionEventProcessor mContentProtectionEventProcessor; + + private static class SessionStateReceiver extends IResultReceiver.Stub { + private final WeakReference<MainContentCaptureSessionV2> mMainSession; + + SessionStateReceiver(MainContentCaptureSessionV2 session) { + mMainSession = new WeakReference<>(session); + } + + @Override + public void send(int resultCode, Bundle resultData) { + final MainContentCaptureSessionV2 mainSession = mMainSession.get(); + if (mainSession == null) { + Log.w(TAG, "received result after mina session released"); + return; + } + final IBinder binder; + if (resultData != null) { + // Change in content capture enabled. + final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE); + if (hasEnabled) { + final boolean disabled = (resultCode == RESULT_CODE_FALSE); + mainSession.mDisabled.set(disabled); + return; + } + binder = resultData.getBinder(EXTRA_BINDER); + if (binder == null) { + Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); + mainSession.runOnContentCaptureThread(() -> mainSession.resetSession( + STATE_DISABLED | STATE_INTERNAL_ERROR)); + return; + } + } else { + binder = null; + } + mainSession.runOnContentCaptureThread(() -> + mainSession.onSessionStarted(resultCode, binder)); + } + } + + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public MainContentCaptureSessionV2( + @NonNull ContentCaptureManager.StrippedContext context, + @NonNull ContentCaptureManager manager, + @NonNull Handler uiHandler, + @NonNull Handler contentCaptureHandler, + @NonNull IContentCaptureManager systemServerInterface) { + mContext = context; + mManager = manager; + mUiHandler = uiHandler; + mContentCaptureHandler = contentCaptureHandler; + mSystemServerInterface = systemServerInterface; + + final int logHistorySize = mManager.mOptions.logHistorySize; + mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null; + + mSessionStateReceiver = new SessionStateReceiver(this); + + mEventProcessQueue = new ConcurrentLinkedQueue<>(); + } + + @Override + ContentCaptureSession getMainCaptureSession() { + return this; + } + + @Override + ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) { + final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext); + internalNotifyChildSessionStarted(mId, child.mId, clientContext); + return child; + } + + /** + * Starts this session. + */ + @Override + void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, + @NonNull ComponentName component, int flags) { + runOnContentCaptureThread( + () -> startImpl(token, shareableActivityToken, component, flags)); + } + + private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, + @NonNull ComponentName component, int flags) { + checkOnContentCaptureThread(); + if (!isContentCaptureEnabled()) return; + + if (sVerbose) { + Log.v(TAG, "start(): token=" + token + ", comp=" + + ComponentName.flattenToShortString(component)); + } + + if (hasStarted()) { + // TODO(b/122959591): make sure this is expected (and when), or use Log.w + if (sDebug) { + Log.d(TAG, "ignoring handleStartSession(" + token + "/" + + ComponentName.flattenToShortString(component) + " while on state " + + getStateAsString(mState)); + } + return; + } + mState = STATE_WAITING_FOR_SERVER; + mApplicationToken = token; + mShareableActivityToken = shareableActivityToken; + mComponentName = component; + + if (sVerbose) { + Log.v(TAG, "handleStartSession(): token=" + token + ", act=" + + getDebugState() + ", id=" + mId); + } + + try { + mSystemServerInterface.startSession(mApplicationToken, mShareableActivityToken, + component, mId, flags, mSessionStateReceiver); + } catch (RemoteException e) { + Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); + } + } + @Override + void onDestroy() { + clearAndRunOnContentCaptureThread(() -> { + try { + flush(FLUSH_REASON_SESSION_FINISHED); + } finally { + destroySession(); + } + }, MSG_FLUSH); + } + + /** + * Callback from {@code system_server} after call to {@link + * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}. + * + * @param resultCode session state + * @param binder handle to {@code IContentCaptureDirectManager} + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public void onSessionStarted(int resultCode, @Nullable IBinder binder) { + checkOnContentCaptureThread(); + if (binder != null) { + mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); + mDirectServiceVulture = () -> { + Log.w(TAG, "Keeping session " + mId + " when service died"); + mState = STATE_SERVICE_DIED; + mDisabled.set(true); + }; + try { + binder.linkToDeath(mDirectServiceVulture, 0); + } catch (RemoteException e) { + Log.w(TAG, "Failed to link to death on " + binder + ": " + e); + } + } + + if (isContentProtectionEnabled()) { + mContentProtectionEventProcessor = + new ContentProtectionEventProcessor( + mManager.getContentProtectionEventBuffer(), + mContentCaptureHandler, + mSystemServerInterface, + mComponentName.getPackageName(), + mManager.mOptions.contentProtectionOptions); + } else { + mContentProtectionEventProcessor = null; + } + + if ((resultCode & STATE_DISABLED) != 0) { + resetSession(resultCode); + } else { + mState = resultCode; + mDisabled.set(false); + // Flush any pending data immediately as buffering forced until now. + flushIfNeeded(FLUSH_REASON_SESSION_CONNECTED); + } + if (sVerbose) { + Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode + + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get() + + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size())); + } + } + + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public void sendEvent(@NonNull ContentCaptureEvent event) { + sendEvent(event, /* forceFlush= */ false); + } + + private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { + checkOnContentCaptureThread(); + final int eventType = event.getType(); + if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); + if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED + && eventType != ContentCaptureEvent.TYPE_CONTEXT_UPDATED) { + // TODO(b/120494182): comment when this could happen (dialogs?) + if (sVerbose) { + Log.v(TAG, "handleSendEvent(" + getDebugState() + ", " + + ContentCaptureEvent.getTypeAsString(eventType) + + "): dropping because session not started yet"); + } + return; + } + if (mDisabled.get()) { + // This happens when the event was queued in the handler before the sesison was ready, + // then handleSessionStarted() returned and set it as disabled - we need to drop it, + // otherwise it will keep triggering handleScheduleFlush() + if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled"); + return; + } + + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + if (eventType == TYPE_VIEW_TREE_APPEARING) { + Trace.asyncTraceBegin( + Trace.TRACE_TAG_VIEW, /* methodName= */ "sendEventAsync", /* cookie= */ 0); + } + } + + if (isContentProtectionReceiverEnabled()) { + sendContentProtectionEvent(event); + } + if (isContentCaptureReceiverEnabled()) { + sendContentCaptureEvent(event, forceFlush); + } + + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + if (eventType == TYPE_VIEW_TREE_APPEARED) { + Trace.asyncTraceEnd( + Trace.TRACE_TAG_VIEW, /* methodName= */ "sendEventAsync", /* cookie= */ 0); + } + } + } + + private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { + checkOnContentCaptureThread(); + if (mContentProtectionEventProcessor != null) { + mContentProtectionEventProcessor.processEvent(event); + } + } + + private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { + checkOnContentCaptureThread(); + final int eventType = event.getType(); + final int maxBufferSize = mManager.mOptions.maxBufferSize; + if (mEvents == null) { + if (sVerbose) { + Log.v(TAG, "handleSendEvent(): creating buffer for " + maxBufferSize + " events"); + } + mEvents = new ArrayList<>(maxBufferSize); + } + + // Some type of events can be merged together + boolean addEvent = true; + + if (eventType == TYPE_VIEW_TEXT_CHANGED) { + // We determine whether to add or merge the current event by following criteria: + // 1. Don't have composing span: always add. + // 2. Have composing span: + // 2.1 either last or current text is empty: add. + // 2.2 last event doesn't have composing span: add. + // Otherwise, merge. + final CharSequence text = event.getText(); + final boolean hasComposingSpan = event.hasComposingSpan(); + if (hasComposingSpan) { + ContentCaptureEvent lastEvent = null; + for (int index = mEvents.size() - 1; index >= 0; index--) { + final ContentCaptureEvent tmpEvent = mEvents.get(index); + if (event.getId().equals(tmpEvent.getId())) { + lastEvent = tmpEvent; + break; + } + } + if (lastEvent != null && lastEvent.hasComposingSpan()) { + final CharSequence lastText = lastEvent.getText(); + final boolean bothNonEmpty = !TextUtils.isEmpty(lastText) + && !TextUtils.isEmpty(text); + boolean equalContent = + TextUtils.equals(lastText, text) + && lastEvent.hasSameComposingSpan(event) + && lastEvent.hasSameSelectionSpan(event); + if (equalContent) { + addEvent = false; + } else if (bothNonEmpty) { + lastEvent.mergeEvent(event); + addEvent = false; + } + if (!addEvent && sVerbose) { + Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text=" + + getSanitizedString(text)); + } + } + } + } + + if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) { + final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1); + if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED + && event.getSessionId() == lastEvent.getSessionId()) { + if (sVerbose) { + Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session " + + lastEvent.getSessionId()); + } + lastEvent.mergeEvent(event); + addEvent = false; + } + } + + if (addEvent) { + mEvents.add(event); + } + + // TODO: we need to change when the flush happens so that we don't flush while the + // composing span hasn't changed. But we might need to keep flushing the events for the + // non-editable views and views that don't have the composing state; otherwise some other + // Content Capture features may be delayed. + + final int numberEvents = mEvents.size(); + + final boolean bufferEvent = numberEvents < maxBufferSize; + + if (bufferEvent && !forceFlush) { + final int flushReason; + if (eventType == TYPE_VIEW_TEXT_CHANGED) { + mNextFlushForTextChanged = true; + flushReason = FLUSH_REASON_TEXT_CHANGE_TIMEOUT; + } else { + if (mNextFlushForTextChanged) { + if (sVerbose) { + Log.i(TAG, "Not scheduling flush because next flush is for text changed"); + } + return; + } + + flushReason = FLUSH_REASON_IDLE_TIMEOUT; + } + scheduleFlush(flushReason, /* checkExisting= */ true); + return; + } + + if (mState != STATE_ACTIVE && numberEvents >= maxBufferSize) { + // Callback from startSession hasn't been called yet - typically happens on system + // apps that are started before the system service + // TODO(b/122959591): try to ignore session while system is not ready / boot + // not complete instead. Similarly, the manager service should return right away + // when the user does not have a service set + if (sDebug) { + Log.d(TAG, "Closing session for " + getDebugState() + + " after " + numberEvents + " delayed events"); + } + resetSession(STATE_DISABLED | STATE_NO_RESPONSE); + // TODO(b/111276913): denylist activity / use special flag to indicate that + // when it's launched again + return; + } + final int flushReason; + switch (eventType) { + case ContentCaptureEvent.TYPE_SESSION_STARTED: + flushReason = FLUSH_REASON_SESSION_STARTED; + break; + case ContentCaptureEvent.TYPE_SESSION_FINISHED: + flushReason = FLUSH_REASON_SESSION_FINISHED; + break; + case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING: + flushReason = FLUSH_REASON_VIEW_TREE_APPEARING; + break; + case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED: + flushReason = FLUSH_REASON_VIEW_TREE_APPEARED; + break; + default: + flushReason = forceFlush ? FLUSH_REASON_FORCE_FLUSH : FLUSH_REASON_FULL; + } + + flush(flushReason); + } + + private boolean hasStarted() { + checkOnContentCaptureThread(); + return mState != UNKNOWN_STATE; + } + + private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { + checkOnContentCaptureThread(); + if (sVerbose) { + Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) + + ", checkExisting=" + checkExisting); + } + if (!hasStarted()) { + if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet"); + return; + } + + if (mDisabled.get()) { + // Should not be called on this state, as handleSendEvent checks. + // But we rather add one if check and log than re-schedule and keep the session alive... + Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called " + + "when disabled. events=" + (mEvents == null ? null : mEvents.size())); + return; + } + if (checkExisting && mContentCaptureHandler.hasMessages(MSG_FLUSH)) { + // "Renew" the flush message by removing the previous one + mContentCaptureHandler.removeMessages(MSG_FLUSH); + } + + final int flushFrequencyMs; + if (reason == FLUSH_REASON_TEXT_CHANGE_TIMEOUT) { + flushFrequencyMs = mManager.mOptions.textChangeFlushingFrequencyMs; + } else { + if (reason != FLUSH_REASON_IDLE_TIMEOUT) { + if (sDebug) { + Log.d(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): not a timeout " + + "reason because mDirectServiceInterface is not ready yet"); + } + } + flushFrequencyMs = mManager.mOptions.idleFlushingFrequencyMs; + } + + mNextFlush = System.currentTimeMillis() + flushFrequencyMs; + if (sVerbose) { + Log.v(TAG, "handleScheduleFlush(): scheduled to flush in " + + flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush)); + } + // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage() + mContentCaptureHandler.postDelayed(() -> + flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); + } + + private void flushIfNeeded(@FlushReason int reason) { + checkOnContentCaptureThread(); + if (mEvents == null || mEvents.isEmpty()) { + if (sVerbose) Log.v(TAG, "Nothing to flush"); + return; + } + flush(reason); + } + + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @Override + public void flush(@FlushReason int reason) { + runOnContentCaptureThread(() -> flushImpl(reason)); + } + + private void flushImpl(@FlushReason int reason) { + checkOnContentCaptureThread(); + if (mEvents == null || mEvents.size() == 0) { + if (sVerbose) { + Log.v(TAG, "Don't flush for empty event buffer."); + } + return; + } + + if (mDisabled.get()) { + Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when " + + "disabled"); + return; + } + + if (!isContentCaptureReceiverEnabled()) { + return; + } + + if (mDirectServiceInterface == null) { + if (sVerbose) { + Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, " + + "client not ready: " + mEvents); + } + if (!mContentCaptureHandler.hasMessages(MSG_FLUSH)) { + scheduleFlush(reason, /* checkExisting= */ false); + } + return; + } + + mNextFlushForTextChanged = false; + + final int numberEvents = mEvents.size(); + final String reasonString = getFlushReasonAsString(reason); + + if (sVerbose) { + ContentCaptureEvent event = mEvents.get(numberEvents - 1); + String forceString = (reason == FLUSH_REASON_FORCE_FLUSH) ? ". The force flush event " + + ContentCaptureEvent.getTypeAsString(event.getType()) : ""; + Log.v(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason) + + forceString); + } + if (mFlushHistory != null) { + // Logs reason, size, max size, idle timeout + final String logRecord = "r=" + reasonString + " s=" + numberEvents + + " m=" + mManager.mOptions.maxBufferSize + + " i=" + mManager.mOptions.idleFlushingFrequencyMs; + mFlushHistory.log(logRecord); + } + try { + mContentCaptureHandler.removeMessages(MSG_FLUSH); + + final ParceledListSlice<ContentCaptureEvent> events = clearEvents(); + mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions); + } catch (RemoteException e) { + Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState() + + ": " + e); + } + } + + @Override + public void updateContentCaptureContext(@Nullable ContentCaptureContext context) { + internalNotifyContextUpdated(mId, context); + } + + /** + * Resets the buffer and return a {@link ParceledListSlice} with the previous events. + */ + @NonNull + private ParceledListSlice<ContentCaptureEvent> clearEvents() { + checkOnContentCaptureThread(); + // NOTE: we must save a reference to the current mEvents and then set it to to null, + // otherwise clearing it would clear it in the receiving side if the service is also local. + if (mEvents == null) { + return new ParceledListSlice<>(Collections.EMPTY_LIST); + } + + final List<ContentCaptureEvent> events = new ArrayList<>(mEvents); + mEvents.clear(); + return new ParceledListSlice<>(events); + } + + /** hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public void destroySession() { + checkOnContentCaptureThread(); + if (sDebug) { + Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " + + getDebugState()); + } + + reportWrongThreadMetric(); + try { + mSystemServerInterface.finishSession(mId); + } catch (RemoteException e) { + Log.e(TAG, "Error destroying system-service session " + mId + " for " + + getDebugState() + ": " + e); + } + + if (mDirectServiceInterface != null) { + mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0); + } + mDirectServiceInterface = null; + mContentProtectionEventProcessor = null; + mEventProcessQueue.clear(); + } + + // TODO(b/122454205): once we support multiple sessions, we might need to move some of these + // clearings out. + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public void resetSession(int newState) { + checkOnContentCaptureThread(); + if (sVerbose) { + Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " + + getStateAsString(mState) + " to " + getStateAsString(newState)); + } + mState = newState; + mDisabled.set((newState & STATE_DISABLED) != 0); + // TODO(b/122454205): must reset children (which currently is owned by superclass) + mApplicationToken = null; + mShareableActivityToken = null; + mComponentName = null; + mEvents = null; + if (mDirectServiceInterface != null) { + try { + mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "IContentCaptureDirectManager does not exist"); + } + } + mDirectServiceInterface = null; + mContentProtectionEventProcessor = null; + mContentCaptureHandler.removeMessages(MSG_FLUSH); + } + + @Override + void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { + final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) + .setViewNode(node.mNode); + enqueueEvent(event); + } + + @Override + void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) { + final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED) + .setAutofillId(id); + enqueueEvent(event); + } + + @Override + void internalNotifyViewTextChanged( + int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { + // Since the same CharSequence instance may be reused in the TextView, we need to make + // a copy of its content so that its value will not be changed by subsequent updates + // in the TextView. + CharSequence trimmed = TextUtils.trimToParcelableSize(text); + final CharSequence eventText = trimmed != null && trimmed == text + ? trimmed.toString() + : trimmed; + + final int composingStart; + final int composingEnd; + if (text instanceof Spannable) { + composingStart = BaseInputConnection.getComposingSpanStart((Spannable) text); + composingEnd = BaseInputConnection.getComposingSpanEnd((Spannable) text); + } else { + composingStart = ContentCaptureEvent.MAX_INVALID_VALUE; + composingEnd = ContentCaptureEvent.MAX_INVALID_VALUE; + } + + final int startIndex = Selection.getSelectionStart(text); + final int endIndex = Selection.getSelectionEnd(text); + + final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) + .setAutofillId(id).setText(eventText) + .setComposingIndex(composingStart, composingEnd) + .setSelectionIndex(startIndex, endIndex); + enqueueEvent(event); + } + + @Override + void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { + final ContentCaptureEvent event = + new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) + .setInsets(viewInsets); + enqueueEvent(event); + } + + @Override + public void internalNotifyViewTreeEvent(int sessionId, boolean started) { + final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; + final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled(); + final boolean forceFlush = disableFlush ? !started : FORCE_FLUSH; + + final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type); + enqueueEvent(event, forceFlush); + } + + @Override + public void internalNotifySessionResumed() { + final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_RESUMED); + enqueueEvent(event, FORCE_FLUSH); + } + + @Override + public void internalNotifySessionPaused() { + final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_PAUSED); + enqueueEvent(event, FORCE_FLUSH); + } + + @Override + boolean isContentCaptureEnabled() { + return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled(); + } + + // Called by ContentCaptureManager.isContentCaptureEnabled + boolean isDisabled() { + return mDisabled.get(); + } + + /** + * Sets the disabled state of content capture. + * + * @return whether disabled state was changed. + */ + boolean setDisabled(boolean disabled) { + return mDisabled.compareAndSet(!disabled, disabled); + } + + @Override + void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, + @NonNull ContentCaptureContext clientContext) { + final ContentCaptureEvent event = + new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) + .setParentSessionId(parentSessionId) + .setClientContext(clientContext); + enqueueEvent(event, FORCE_FLUSH); + } + + @Override + void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) { + final ContentCaptureEvent event = + new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) + .setParentSessionId(parentSessionId); + enqueueEvent(event, FORCE_FLUSH); + } + + @Override + void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { + final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) + .setClientContext(context); + enqueueEvent(event, FORCE_FLUSH); + } + + @Override + public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { + final ContentCaptureEvent event = + new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) + .setBounds(bounds); + enqueueEvent(event); + } + + private List<ContentCaptureEvent> clearBufferEvents() { + final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>(); + ContentCaptureEvent event; + while ((event = mEventProcessQueue.poll()) != null) { + bufferEvents.add(event); + } + return bufferEvents; + } + + private void enqueueEvent(@NonNull final ContentCaptureEvent event) { + enqueueEvent(event, /* forceFlush */ false); + } + + /** + * Enqueue the event into {@code mEventProcessBuffer} if it is not an urgent request. Otherwise, + * clear the buffer events then starting sending out current event. + */ + private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) { + if (forceFlush) { + // The buffer events are cleared in the same thread first to prevent new events + // being added during the time of context switch. This would disrupt the sequence + // of events. + final List<ContentCaptureEvent> batchEvents = clearBufferEvents(); + runOnContentCaptureThread(() -> { + for (int i = 0; i < batchEvents.size(); i++) { + sendEvent(batchEvents.get(i)); + } + sendEvent(event, /* forceFlush= */ true); + }); + } else { + mEventProcessQueue.offer(event); + } + } + + @Override + public void notifyContentCaptureEvents( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { + runOnUiThread(() -> { + prepareViewStructures(contentCaptureEvents); + runOnContentCaptureThread(() -> + notifyContentCaptureEventsImpl(contentCaptureEvents)); + }); + } + + /** + * Traverse events and pre-process {@link View} events to {@link ViewStructureSession} events. + * If a {@link View} event is invalid, an empty {@link ViewStructureSession} will still be + * provided. + */ + private void prepareViewStructures( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { + for (int i = 0; i < contentCaptureEvents.size(); i++) { + int sessionId = contentCaptureEvents.keyAt(i); + ArrayList<Object> events = contentCaptureEvents.valueAt(i); + for_each_event: for (int j = 0; j < events.size(); j++) { + Object event = events.get(j); + if (event instanceof View) { + View view = (View) event; + ContentCaptureSession session = view.getContentCaptureSession(); + ViewStructureSession structureSession = new ViewStructureSession(); + + // Replace the View event with ViewStructureSession no matter the data is + // available or not. This is to ensure the sequence of the events are still + // the same. Calls to notifyViewAppeared will check the availability later. + events.set(j, structureSession); + if (session == null) { + Log.w(TAG, "no content capture session on view: " + view); + continue for_each_event; + } + int actualId = session.getId(); + if (actualId != sessionId) { + Log.w(TAG, "content capture session mismatch for view (" + view + + "): was " + sessionId + " before, it's " + actualId + " now"); + continue for_each_event; + } + ViewStructure structure = session.newViewStructure(view); + view.onProvideContentCaptureStructure(structure, /* flags= */ 0); + + structureSession.setSession(session); + structureSession.setStructure(structure); + } + } + } + } + + private void notifyContentCaptureEventsImpl( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { + checkOnContentCaptureThread(); + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); + } + for (int i = 0; i < contentCaptureEvents.size(); i++) { + int sessionId = contentCaptureEvents.keyAt(i); + internalNotifyViewTreeEvent(sessionId, /* started= */ true); + ArrayList<Object> events = contentCaptureEvents.valueAt(i); + for_each_event: for (int j = 0; j < events.size(); j++) { + Object event = events.get(j); + if (event instanceof AutofillId) { + internalNotifyViewDisappeared(sessionId, (AutofillId) event); + } else if (event instanceof ViewStructureSession viewStructureSession) { + viewStructureSession.notifyViewAppeared(); + } else if (event instanceof Insets) { + internalNotifyViewInsetsChanged(sessionId, (Insets) event); + } else { + Log.w(TAG, "invalid content capture event: " + event); + } + } + internalNotifyViewTreeEvent(sessionId, /* started= */ false); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + + @Override + void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + super.dump(prefix, pw); + + pw.print(prefix); pw.print("mContext: "); pw.println(mContext); + pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId()); + if (mDirectServiceInterface != null) { + pw.print(prefix); pw.print("mDirectServiceInterface: "); + pw.println(mDirectServiceInterface); + } + pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get()); + pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled()); + pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState)); + if (mApplicationToken != null) { + pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken); + } + if (mShareableActivityToken != null) { + pw.print(prefix); pw.print("sharable activity token: "); + pw.println(mShareableActivityToken); + } + if (mComponentName != null) { + pw.print(prefix); pw.print("component name: "); + pw.println(mComponentName.flattenToShortString()); + } + if (mEvents != null && !mEvents.isEmpty()) { + final int numberEvents = mEvents.size(); + pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents); + pw.print('/'); pw.println(mManager.mOptions.maxBufferSize); + if (sVerbose && numberEvents > 0) { + final String prefix3 = prefix + " "; + for (int i = 0; i < numberEvents; i++) { + final ContentCaptureEvent event = mEvents.get(i); + pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw); + pw.println(); + } + } + pw.print(prefix); pw.print("mNextFlushForTextChanged: "); + pw.println(mNextFlushForTextChanged); + pw.print(prefix); pw.print("flush frequency: "); + if (mNextFlushForTextChanged) { + pw.println(mManager.mOptions.textChangeFlushingFrequencyMs); + } else { + pw.println(mManager.mOptions.idleFlushingFrequencyMs); + } + pw.print(prefix); pw.print("next flush: "); + TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw); + pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")"); + } + if (mFlushHistory != null) { + pw.print(prefix); pw.println("flush history:"); + mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println(); + } else { + pw.print(prefix); pw.println("not logging flush history"); + } + + super.dump(prefix, pw); + } + + /** + * Gets a string that can be used to identify the activity on logging statements. + */ + private String getActivityName() { + return mComponentName == null + ? "pkg:" + mContext.getPackageName() + : "act:" + mComponentName.flattenToShortString(); + } + + @NonNull + private String getDebugState() { + return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled=" + + mDisabled.get() + "]"; + } + + @NonNull + private String getDebugState(@FlushReason int reason) { + return getDebugState() + ", reason=" + getFlushReasonAsString(reason); + } + + private boolean isContentProtectionReceiverEnabled() { + return mManager.mOptions.contentProtectionOptions.enableReceiver; + } + + private boolean isContentCaptureReceiverEnabled() { + return mManager.mOptions.enableReceiver; + } + + private boolean isContentProtectionEnabled() { + // Should not be possible for mComponentName to be null here but check anyway + // Should not be possible for groups to be empty if receiver is enabled but check anyway + return mManager.mOptions.contentProtectionOptions.enableReceiver + && mManager.getContentProtectionEventBuffer() != null + && mComponentName != null + && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() + || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); + } + + /** + * Checks that the current work is running on the assigned thread from {@code mHandler} and + * count the number of times running on the wrong thread. + * + * <p>It is not guaranteed that the callers always invoke function from a single thread. + * Therefore, accessing internal properties in {@link MainContentCaptureSession} should + * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> + */ + private void checkOnContentCaptureThread() { + final boolean onContentCaptureThread = mContentCaptureHandler.getLooper().isCurrentThread(); + if (!onContentCaptureThread) { + mWrongThreadCount.incrementAndGet(); + Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread()); + } + } + + /** Reports number of times running on the wrong thread. */ + private void reportWrongThreadMetric() { + Counter.logIncrement( + CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID, mWrongThreadCount.getAndSet(0)); + } + + /** + * Ensures that {@code r} will be running on the assigned thread. + * + * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable. + * </p> + */ + private void runOnContentCaptureThread(@NonNull Runnable r) { + if (!mContentCaptureHandler.getLooper().isCurrentThread()) { + mContentCaptureHandler.post(r); + } else { + r.run(); + } + } + + private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { + if (!mContentCaptureHandler.getLooper().isCurrentThread()) { + mContentCaptureHandler.removeMessages(what); + mContentCaptureHandler.post(r); + } else { + r.run(); + } + } + + private void runOnUiThread(@NonNull Runnable r) { + if (mUiHandler.getLooper().isCurrentThread()) { + r.run(); + } else { + mUiHandler.post(r); + } + } + + /** + * Holds {@link ContentCaptureSession} and related {@link ViewStructure} for processing. + */ + private static final class ViewStructureSession { + @Nullable private ContentCaptureSession mSession; + @Nullable private ViewStructure mStructure; + + ViewStructureSession() {} + + void setSession(@Nullable ContentCaptureSession session) { + this.mSession = session; + } + + void setStructure(@Nullable ViewStructure struct) { + this.mStructure = struct; + } + + /** + * Calls {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)} if the session and + * the view structure are available. + */ + void notifyViewAppeared() { + if (mSession != null && mStructure != null) { + mSession.notifyViewAppeared(mStructure); + } + } + } +} diff --git a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java index f05d9cbe1c8d..983658a01dc8 100644 --- a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java +++ b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java @@ -96,8 +96,10 @@ public class PackageInfoCommonUtils { info.baseRevisionCode = pkg.getBaseRevisionCode(); info.splitRevisionCodes = pkg.getSplitRevisionCodes(); info.versionName = pkg.getVersionName(); - info.sharedUserId = pkg.getSharedUserId(); - info.sharedUserLabel = pkg.getSharedUserLabelResourceId(); + if (!pkg.isLeavingSharedUser()) { + info.sharedUserId = pkg.getSharedUserId(); + info.sharedUserLabel = pkg.getSharedUserLabelResourceId(); + } info.applicationInfo = applicationInfo; info.installLocation = pkg.getInstallLocation(); if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c6209dd25c47..232a36fb6cb3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -578,6 +578,7 @@ <protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" /> <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" /> + <protected-broadcast android:name="android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED" /> <protected-broadcast android:name="NotificationManagerService.TIMEOUT" /> <protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" /> <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" /> diff --git a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java deleted file mode 100644 index 785a8a1ced60..000000000000 --- a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2023 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 android.app.servertransaction; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.doReturn; - -import android.app.Activity; -import android.app.ClientTransactionHandler; -import android.content.Context; -import android.content.res.Configuration; -import android.os.IBinder; -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link ActivityConfigurationChangeItem}. - * - * Build/Install/Run: - * atest FrameworksCoreTests:ActivityConfigurationChangeItemTest - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class ActivityConfigurationChangeItemTest { - - @Mock - private ClientTransactionHandler mHandler; - @Mock - private IBinder mToken; - @Mock - private Activity mActivity; - // Can't mock final class. - private final Configuration mConfiguration = new Configuration(); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testGetContextToUpdate() { - doReturn(mActivity).when(mHandler).getActivity(mToken); - - final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem - .obtain(mToken, mConfiguration); - final Context context = item.getContextToUpdate(mHandler); - - assertEquals(mActivity, context); - } -} diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java new file mode 100644 index 000000000000..b5e8203c51ed --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2023 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 android.app.servertransaction; + +import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.view.Display.DEFAULT_DISPLAY; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.app.ActivityThread; +import android.app.ClientTransactionHandler; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; +import android.window.WindowContext; +import android.window.WindowContextInfo; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for subtypes of {@link ClientTransactionItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:ClientTransactionItemTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class ClientTransactionItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private IBinder mActivityToken; + @Mock + private Activity mActivity; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IBinder mWindowClientToken; + @Mock + private WindowContext mWindowContext; + @Mock + private IWindow mWindow; + + // Can't mock final class. + private Configuration mGlobalConfig; + private Configuration mConfiguration; + private ActivityThread.ActivityClientRecord mActivityClientRecord; + private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed; + private InsetsState mInsetsState; + private ClientWindowFrames mFrames; + private MergedConfiguration mMergedConfiguration; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mGlobalConfig = new Configuration(); + mConfiguration = new Configuration(); + mActivitiesToBeDestroyed = new ArrayMap<>(); + mActivityClientRecord = new ActivityThread.ActivityClientRecord(); + mInsetsState = new InsetsState(); + mFrames = new ClientWindowFrames(); + mMergedConfiguration = new MergedConfiguration(mGlobalConfig, mConfiguration); + + doReturn(mActivity).when(mHandler).getActivity(mActivityToken); + doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed(); + } + + @Test + public void testActivityConfigurationChangeItem_getContextToUpdate() { + final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem + .obtain(mActivityToken, mConfiguration); + final Context context = item.getContextToUpdate(mHandler); + + assertEquals(mActivity, context); + } + + @Test + public void testActivityRelaunchItem_getContextToUpdate() { + final ActivityRelaunchItem item = ActivityRelaunchItem + .obtain(mActivityToken, null /* pendingResults */, null /* pendingNewIntents */, + 0 /* configChange */, mMergedConfiguration, false /* preserveWindow */); + final Context context = item.getContextToUpdate(mHandler); + + assertEquals(mActivity, context); + } + + @Test + public void testConfigurationChangeItem_getContextToUpdate() { + final ConfigurationChangeItem item = ConfigurationChangeItem + .obtain(mConfiguration, DEVICE_ID_DEFAULT); + final Context context = item.getContextToUpdate(mHandler); + + assertEquals(ActivityThread.currentApplication(), context); + } + + @Test + public void testDestroyActivityItem_preExecute() { + final DestroyActivityItem item = DestroyActivityItem + .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + item.preExecute(mHandler); + + assertEquals(1, mActivitiesToBeDestroyed.size()); + assertEquals(item, mActivitiesToBeDestroyed.get(mActivityToken)); + } + + @Test + public void testDestroyActivityItem_postExecute() { + final DestroyActivityItem item = DestroyActivityItem + .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + item.preExecute(mHandler); + item.postExecute(mHandler, mPendingActions); + + assertTrue(mActivitiesToBeDestroyed.isEmpty()); + } + + @Test + public void testDestroyActivityItem_execute() { + final DestroyActivityItem item = DestroyActivityItem + .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + item.execute(mHandler, mActivityClientRecord, mPendingActions); + + verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */, + eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any()); + } + + @Test + public void testLaunchActivityItem_getContextToUpdate() { + final LaunchActivityItem item = new TestUtils.LaunchActivityItemBuilder( + mActivityToken, new Intent(), new ActivityInfo()) + .build(); + + final Context context = item.getContextToUpdate(mHandler); + + assertEquals(ActivityThread.currentApplication(), context); + } + + @Test + public void testMoveToDisplayItem_getContextToUpdate() { + final MoveToDisplayItem item = MoveToDisplayItem + .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration); + final Context context = item.getContextToUpdate(mHandler); + + assertEquals(mActivity, context); + } + + @Test + public void testWindowContextInfoChangeItem_execute() { + final WindowContextInfoChangeItem item = WindowContextInfoChangeItem + .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY); + item.execute(mHandler, mPendingActions); + + verify(mHandler).handleWindowContextInfoChanged(mWindowClientToken, + new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY)); + } + + @Test + public void testWindowContextInfoChangeItem_getContextToUpdate() { + doReturn(mWindowContext).when(mHandler).getWindowContext(mWindowClientToken); + + final WindowContextInfoChangeItem item = WindowContextInfoChangeItem + .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY); + final Context context = item.getContextToUpdate(mHandler); + + assertEquals(mWindowContext, context); + } + + @Test + public void testWindowContextWindowRemovalItem_execute() { + final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain( + mWindowClientToken); + item.execute(mHandler, mPendingActions); + + verify(mHandler).handleWindowContextWindowRemoval(mWindowClientToken); + } + + @Test + public void testWindowStateResizeItem_execute() throws RemoteException { + final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames, + true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + item.execute(mHandler, mPendingActions); + + verify(mWindow).resized(mFrames, + true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + } + + @Test + public void testWindowStateResizeItem_getContextToUpdate() { + final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames, + true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + final Context context = item.getContextToUpdate(mHandler); + + assertEquals(ActivityThread.currentApplication(), context); + } + +} diff --git a/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java deleted file mode 100644 index d9f5523c9782..000000000000 --- a/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2023 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 android.app.servertransaction; - -import static android.content.Context.DEVICE_ID_DEFAULT; - -import static org.junit.Assert.assertEquals; - -import android.app.ActivityThread; -import android.app.ClientTransactionHandler; -import android.content.Context; -import android.content.res.Configuration; -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link ConfigurationChangeItem}. - * - * Build/Install/Run: - * atest FrameworksCoreTests:ConfigurationChangeItemTest - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class ConfigurationChangeItemTest { - - @Mock - private ClientTransactionHandler mHandler; - // Can't mock final class. - private final Configuration mConfiguration = new Configuration(); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testGetContextToUpdate() { - final ConfigurationChangeItem item = ConfigurationChangeItem - .obtain(mConfiguration, DEVICE_ID_DEFAULT); - final Context context = item.getContextToUpdate(mHandler); - - assertEquals(ActivityThread.currentApplication(), context); - } -} diff --git a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java deleted file mode 100644 index ecd75a8370ce..000000000000 --- a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2023 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 android.app.servertransaction; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; - -import android.app.ActivityThread.ActivityClientRecord; -import android.app.ClientTransactionHandler; -import android.os.IBinder; -import android.platform.test.annotations.Presubmit; -import android.util.ArrayMap; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link DestroyActivityItem}. - * - * Build/Install/Run: - * atest FrameworksCoreTests:DestroyActivityItemTest - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class DestroyActivityItemTest { - - @Mock - private ClientTransactionHandler mHandler; - @Mock - private PendingTransactionActions mPendingActions; - @Mock - private IBinder mActivityToken; - - // Can't mock final class. - private ActivityClientRecord mActivityClientRecord; - - private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed; - private DestroyActivityItem mItem; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mItem = DestroyActivityItem.obtain( - mActivityToken, false /* finished */, 123 /* configChanges */); - mActivityClientRecord = new ActivityClientRecord(); - mActivitiesToBeDestroyed = new ArrayMap<>(); - - doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed(); - } - - @Test - public void testPreExecute() { - mItem.preExecute(mHandler); - - assertEquals(1, mActivitiesToBeDestroyed.size()); - assertEquals(mItem, mActivitiesToBeDestroyed.get(mActivityToken)); - } - - @Test - public void testPostExecute() { - mItem.preExecute(mHandler); - mItem.postExecute(mHandler, mPendingActions); - - assertTrue(mActivitiesToBeDestroyed.isEmpty()); - } - - @Test - public void testExecute() { - mItem.execute(mHandler, mActivityClientRecord, mPendingActions); - - verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */, - eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any()); - } -} diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java deleted file mode 100644 index a801a76d16c9..000000000000 --- a/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2023 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 android.app.servertransaction; - -import static android.view.Display.DEFAULT_DISPLAY; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; - -import android.app.ClientTransactionHandler; -import android.content.Context; -import android.content.res.Configuration; -import android.os.IBinder; -import android.platform.test.annotations.Presubmit; -import android.window.WindowContext; -import android.window.WindowContextInfo; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link WindowContextInfoChangeItem}. - * - * Build/Install/Run: - * atest FrameworksCoreTests:WindowContextInfoChangeItemTest - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class WindowContextInfoChangeItemTest { - - @Mock - private ClientTransactionHandler mHandler; - @Mock - private PendingTransactionActions mPendingActions; - @Mock - private IBinder mClientToken; - @Mock - private WindowContext mWindowContext; - // Can't mock final class. - private final Configuration mConfiguration = new Configuration(); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testExecute() { - final WindowContextInfoChangeItem item = WindowContextInfoChangeItem - .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY); - item.execute(mHandler, mPendingActions); - - verify(mHandler).handleWindowContextInfoChanged(mClientToken, - new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY)); - } - - @Test - public void testGetContextToUpdate() { - doReturn(mWindowContext).when(mHandler).getWindowContext(mClientToken); - - final WindowContextInfoChangeItem item = WindowContextInfoChangeItem - .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY); - final Context context = item.getContextToUpdate(mHandler); - - assertEquals(mWindowContext, context); - } -} diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java deleted file mode 100644 index cf9935f2822f..000000000000 --- a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2023 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 android.app.servertransaction; - -import static org.mockito.Mockito.verify; - -import android.app.ClientTransactionHandler; -import android.os.IBinder; -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link WindowContextWindowRemovalItem}. - * - * Build/Install/Run: - * atest FrameworksCoreTests:WindowContextWindowRemovalItemTest - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class WindowContextWindowRemovalItemTest { - - @Mock - private ClientTransactionHandler mHandler; - @Mock - private PendingTransactionActions mPendingActions; - @Mock - private IBinder mClientToken; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testExecute() { - final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain( - mClientToken); - item.execute(mHandler, mPendingActions); - - verify(mHandler).handleWindowContextWindowRemoval(mClientToken); - } -} diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java deleted file mode 100644 index 4d45daf3570c..000000000000 --- a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2023 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 android.app.servertransaction; - -import static org.mockito.Mockito.verify; - -import android.app.ClientTransactionHandler; -import android.os.RemoteException; -import android.platform.test.annotations.Presubmit; -import android.util.MergedConfiguration; -import android.view.IWindow; -import android.view.InsetsState; -import android.window.ClientWindowFrames; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link WindowStateResizeItem}. - * - * Build/Install/Run: - * atest FrameworksCoreTests:WindowStateResizeItemTest - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class WindowStateResizeItemTest { - - @Mock - private ClientTransactionHandler mHandler; - @Mock - private PendingTransactionActions mPendingActions; - @Mock - private IWindow mWindow; - - private InsetsState mInsetsState; - private ClientWindowFrames mFrames; - private MergedConfiguration mConfiguration; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - mInsetsState = new InsetsState(); - mFrames = new ClientWindowFrames(); - mConfiguration = new MergedConfiguration(); - } - - @Test - public void testExecute() throws RemoteException { - final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames, - true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, - true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, - true /* dragResizing */); - item.execute(mHandler, mPendingActions); - - verify(mWindow).resized(mFrames, - true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, - true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, - true /* dragResizing */); - } -} diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java index 35ddfdb3723b..e52aa1b0fff4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -153,7 +153,7 @@ public class ContentCaptureManagerTest { final ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS); // Ensure main session is created. - final MainContentCaptureSession unused = manager.getMainContentCaptureSession(); + final ContentCaptureSession unused = manager.getMainContentCaptureSession(); final WindowManager.LayoutParams initialParam = new WindowManager.LayoutParams(); initialParam.flags |= WindowManager.LayoutParams.FLAG_SECURE; @@ -167,7 +167,7 @@ public class ContentCaptureManagerTest { final ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS); // Ensure main session is created. - final MainContentCaptureSession unused = manager.getMainContentCaptureSession(); + final ContentCaptureSession unused = manager.getMainContentCaptureSession(); final WindowManager.LayoutParams initialParam = new WindowManager.LayoutParams(); initialParam.flags |= WindowManager.LayoutParams.FLAG_SECURE; // Default param does not have FLAG_SECURE set. @@ -184,7 +184,7 @@ public class ContentCaptureManagerTest { final ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS); // Ensure main session is created. - final MainContentCaptureSession unused = manager.getMainContentCaptureSession(); + final ContentCaptureSession unused = manager.getMainContentCaptureSession(); // Default param does not have FLAG_SECURE set. final WindowManager.LayoutParams resetParam = new WindowManager.LayoutParams(); diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 23b9b9bdb451..4a4c693d7122 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -21,12 +21,19 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentName; import android.graphics.Insets; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.SparseArray; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.google.common.collect.ImmutableMap; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; @@ -40,6 +47,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.ArrayList; import java.util.Map; /** @@ -195,6 +203,22 @@ public class ContentCaptureSessionTest { } @Override + void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, + @NonNull ComponentName component, int flags) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + boolean isDisabled() { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + boolean setDisabled(boolean disabled) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override ContentCaptureSession newChild(ContentCaptureContext context) { throw new UnsupportedOperationException("should not have been called"); } @@ -210,20 +234,20 @@ public class ContentCaptureSessionTest { } @Override - void internalNotifyViewAppeared(ViewStructureImpl node) { + void internalNotifyViewAppeared(final int sessionId, ViewStructureImpl node) { throw new UnsupportedOperationException("should not have been called"); } @Override - void internalNotifyViewDisappeared(AutofillId id) {} + void internalNotifyViewDisappeared(final int sessionId, AutofillId id) {} @Override - void internalNotifyViewTextChanged(AutofillId id, CharSequence text) { + void internalNotifyViewTextChanged(final int sessionId, AutofillId id, CharSequence text) { throw new UnsupportedOperationException("should not have been called"); } @Override - public void internalNotifyViewTreeEvent(boolean started) { + public void internalNotifyViewTreeEvent(final int sessionId, boolean started) { if (started) { mInternalNotifyViewTreeEventStartedCount += 1; } else { @@ -242,7 +266,34 @@ public class ContentCaptureSessionTest { } @Override - void internalNotifyViewInsetsChanged(Insets viewInsets) { + void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, + @NonNull ContentCaptureContext clientContext) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + public void notifyContentCaptureEvents( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { + + } + + @Override + void internalNotifyViewInsetsChanged(final int sessionId, Insets viewInsets) { throw new UnsupportedOperationException("should not have been called"); } diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java new file mode 100644 index 000000000000..f0f3a9683353 --- /dev/null +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2023 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 android.view.contentcapture; + +import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; +import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED; +import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.content.ComponentName; +import android.content.ContentCaptureOptions; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.graphics.Insets; +import android.os.Handler; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.SparseArray; +import android.view.View; +import android.view.autofill.AutofillId; +import android.view.contentprotection.ContentProtectionEventProcessor; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Test for {@link MainContentCaptureSessionV2}. + * + * <p>Run with: {@code atest + * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionV2Test} + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +@TestableLooper.RunWithLooper +public class MainContentCaptureSessionV2Test { + + private static final int BUFFER_SIZE = 100; + + private static final int REASON = 123; + + private static final ContentCaptureEvent EVENT = + new ContentCaptureEvent(/* sessionId= */ 0, TYPE_SESSION_STARTED); + + private static final ComponentName COMPONENT_NAME = + new ComponentName("com.test.package", "TestClass"); + + private static final Context sContext = ApplicationProvider.getApplicationContext(); + + private static final ContentCaptureManager.StrippedContext sStrippedContext = + new ContentCaptureManager.StrippedContext(sContext); + + private TestableLooper mTestableLooper; + + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock private IContentCaptureManager mMockSystemServerInterface; + + @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor; + + @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager; + + @Before + public void setup() { + mTestableLooper = TestableLooper.get(this); + } + + @Test + public void onSessionStarted_contentProtectionEnabled_processorCreated() { + MainContentCaptureSessionV2 session = createSession(); + assertThat(session.mContentProtectionEventProcessor).isNull(); + + session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); + + assertThat(session.mContentProtectionEventProcessor).isNotNull(); + } + + @Test + public void onSessionStarted_contentProtectionDisabled_processorNotCreated() { + MainContentCaptureSessionV2 session = + createSession( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ false); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); + + assertThat(session.mContentProtectionEventProcessor).isNull(); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + } + + @Test + public void onSessionStarted_contentProtectionNoBuffer_processorNotCreated() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + new ContentCaptureOptions.ContentProtectionOptions( + /* enableReceiver= */ true, + -BUFFER_SIZE, + /* requiredGroups= */ List.of(List.of("a")), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); + MainContentCaptureSessionV2 session = createSession(options); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); + + assertThat(session.mContentProtectionEventProcessor).isNull(); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + } + + @Test + public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + new ContentCaptureOptions.ContentProtectionOptions( + /* enableReceiver= */ true, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); + MainContentCaptureSessionV2 session = createSession(options); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); + + assertThat(session.mContentProtectionEventProcessor).isNull(); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + } + + @Test + public void onSessionStarted_noComponentName_processorNotCreated() { + MainContentCaptureSessionV2 session = createSession(); + session.mComponentName = null; + + session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); + + assertThat(session.mContentProtectionEventProcessor).isNull(); + } + + @Test + public void sendEvent_contentCaptureDisabled_contentProtectionDisabled() { + MainContentCaptureSessionV2 session = + createSession( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ false); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNull(); + } + + @Test + public void sendEvent_contentCaptureDisabled_contentProtectionEnabled() { + MainContentCaptureSessionV2 session = + createSession( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ true); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); + + verify(mMockContentProtectionEventProcessor).processEvent(EVENT); + assertThat(session.mEvents).isNull(); + } + + @Test + public void sendEvent_contentCaptureEnabled_contentProtectionDisabled() { + MainContentCaptureSessionV2 session = + createSession( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ false); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNotNull(); + assertThat(session.mEvents).containsExactly(EVENT); + } + + @Test + public void sendEvent_contentCaptureEnabled_contentProtectionEnabled() { + MainContentCaptureSessionV2 session = createSession(); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); + + verify(mMockContentProtectionEventProcessor).processEvent(EVENT); + assertThat(session.mEvents).isNotNull(); + assertThat(session.mEvents).containsExactly(EVENT); + } + + @Test + public void sendEvent_contentProtectionEnabled_processorNotCreated() { + MainContentCaptureSessionV2 session = + createSession( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ true); + + session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNull(); + } + + @Test + public void flush_contentCaptureDisabled_contentProtectionDisabled() throws Exception { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ false); + MainContentCaptureSessionV2 session = createSession(options); + session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.flush(REASON); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyZeroInteractions(mMockContentCaptureDirectManager); + assertThat(session.mEvents).containsExactly(EVENT); + } + + @Test + public void flush_contentCaptureDisabled_contentProtectionEnabled() { + MainContentCaptureSessionV2 session = + createSession( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ true); + session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.flush(REASON); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyZeroInteractions(mMockContentCaptureDirectManager); + assertThat(session.mEvents).containsExactly(EVENT); + } + + @Test + public void flush_contentCaptureEnabled_contentProtectionDisabled() throws Exception { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ false); + MainContentCaptureSessionV2 session = createSession(options); + session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.flush(REASON); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isEmpty(); + assertEventFlushedContentCapture(options); + } + + @Test + public void flush_contentCaptureEnabled_contentProtectionEnabled() throws Exception { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSessionV2 session = createSession(options); + session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.flush(REASON); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isEmpty(); + assertEventFlushedContentCapture(options); + } + + @Test + public void destroySession() throws Exception { + MainContentCaptureSessionV2 session = createSession(); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.destroySession(); + mTestableLooper.processAllMessages(); + + verify(mMockSystemServerInterface).finishSession(anyInt()); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mDirectServiceInterface).isNull(); + assertThat(session.mContentProtectionEventProcessor).isNull(); + } + + @Test + public void resetSession() { + MainContentCaptureSessionV2 session = createSession(); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.resetSession(/* newState= */ 0); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockSystemServerInterface); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mDirectServiceInterface).isNull(); + assertThat(session.mContentProtectionEventProcessor).isNull(); + } + + @Test + @SuppressWarnings("GuardedBy") + public void notifyContentCaptureEvents_notStarted_ContentCaptureDisabled_ProtectionDisabled() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ false); + MainContentCaptureSessionV2 session = createSession(options); + + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentCaptureDirectManager); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNull(); + } + + @Test + @SuppressWarnings("GuardedBy") + public void notifyContentCaptureEvents_started_ContentCaptureDisabled_ProtectionDisabled() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ false); + MainContentCaptureSessionV2 session = createSession(options); + + session.onSessionStarted(0x2, null); + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentCaptureDirectManager); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNull(); + } + + @Test + @SuppressWarnings("GuardedBy") + public void notifyContentCaptureEvents_notStarted_ContentCaptureEnabled_ProtectionEnabled() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSessionV2 session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentCaptureDirectManager); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNull(); + } + + @Test + @SuppressWarnings("GuardedBy") + public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled() + throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSessionV2 session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.onSessionStarted(0x2, null); + // Override the processor for interaction verification. + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + // Force flush will happen twice. + verify(mMockContentCaptureDirectManager, times(1)) + .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARING), any()); + verify(mMockContentCaptureDirectManager, times(1)) + .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARED), any()); + // Other than the five view events, there will be two additional tree appearing events. + verify(mMockContentProtectionEventProcessor, times(7)).processEvent(any()); + assertThat(session.mEvents).isEmpty(); + } + + /** Simulates the regular content capture events sequence. */ + private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) { + final ArrayList<Object> events = new ArrayList<>( + List.of( + prepareView(session), + prepareView(session), + new AutofillId(0), + prepareView(session), + Insets.of(0, 0, 0, 0) + ) + ); + + final SparseArray<ArrayList<Object>> contentCaptureEvents = new SparseArray<>(); + contentCaptureEvents.set(session.getId(), events); + + session.notifyContentCaptureEvents(contentCaptureEvents); + } + + private View prepareView(final MainContentCaptureSessionV2 session) { + final View view = new View(sContext); + view.setContentCaptureSession(session); + return view; + } + + private static ContentCaptureOptions createOptions( + boolean enableContentCaptureReceiver, + ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) { + return new ContentCaptureOptions( + /* loggingLevel= */ 0, + BUFFER_SIZE, + /* idleFlushingFrequencyMs= */ 0, + /* textChangeFlushingFrequencyMs= */ 0, + /* logHistorySize= */ 0, + /* disableFlushForViewTreeAppearing= */ false, + enableContentCaptureReceiver, + contentProtectionOptions, + /* whitelistedComponents= */ null); + } + + private static ContentCaptureOptions createOptions( + boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) { + return createOptions( + enableContentCaptureReceiver, + new ContentCaptureOptions.ContentProtectionOptions( + enableContentProtectionReceiver, + BUFFER_SIZE, + /* requiredGroups= */ List.of(List.of("a")), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); + } + + private ContentCaptureManager createManager(ContentCaptureOptions options) { + return new ContentCaptureManager(sContext, mMockSystemServerInterface, options); + } + + private MainContentCaptureSessionV2 createSession(ContentCaptureManager manager) { + final Handler testHandler = Handler.createAsync(mTestableLooper.getLooper()); + MainContentCaptureSessionV2 session = + new MainContentCaptureSessionV2( + sStrippedContext, + manager, + testHandler, + testHandler, + mMockSystemServerInterface); + session.mComponentName = COMPONENT_NAME; + return session; + } + + private MainContentCaptureSessionV2 createSession(ContentCaptureOptions options) { + return createSession(createManager(options)); + } + + private MainContentCaptureSessionV2 createSession( + boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) { + return createSession( + createOptions(enableContentCaptureReceiver, enableContentProtectionReceiver)); + } + + private MainContentCaptureSessionV2 createSession() { + return createSession( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + } + + private void assertEventFlushedContentCapture(ContentCaptureOptions options) throws Exception { + ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class); + verify(mMockContentCaptureDirectManager) + .sendEvents(captor.capture(), eq(REASON), eq(options)); + + assertThat(captor.getValue()).isNotNull(); + List<ContentCaptureEvent> actual = captor.getValue().getList(); + assertThat(actual).isNotNull(); + assertThat(actual).containsExactly(EVENT); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 15ee4e1d4adf..65597de44255 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -47,6 +47,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.getActivityInt import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; +import android.annotation.CallbackExecutor; import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityOptions; @@ -63,6 +64,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.SystemProperties; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -85,6 +87,7 @@ import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.extensions.WindowExtensionsImpl; import androidx.window.extensions.core.util.function.Consumer; import androidx.window.extensions.core.util.function.Function; +import androidx.window.extensions.core.util.function.Predicate; import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; import androidx.window.extensions.layout.WindowLayoutComponentImpl; @@ -158,8 +161,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** Callback to Jetpack to notify about changes to split states. */ @GuardedBy("mLock") @Nullable - private Consumer<List<SplitInfo>> mEmbeddingCallback; + private Consumer<List<SplitInfo>> mSplitInfoCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); + + /** + * Stores callbacks to Jetpack to notify about changes to {@link ActivityStack activityStacks} + * and corresponding {@link Executor executors} to dispatch the callback. + */ + @GuardedBy("mLock") + @NonNull + private final ArrayMap<Consumer<List<ActivityStack>>, Executor> mActivityStackCallbacks = + new ArrayMap<>(); + + private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>(); + private final Handler mHandler; final Object mLock = new Object(); private final ActivityStartMonitor mActivityStartMonitor; @@ -283,7 +298,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void unpinTopActivityStack(int taskId){ + public void unpinTopActivityStack(int taskId) { synchronized (mLock) { Log.i(TAG, "Request to unpin top activity stack."); final TaskContainer task = getTaskContainer(taskId); @@ -333,6 +348,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen public void setActivityStackAttributesCalculator( @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes> calculator) { + if (!Flags.activityEmbeddingOverlayPresentationFlag()) { + return; + } synchronized (mLock) { mActivityStackAttributesCalculator = calculator; } @@ -340,6 +358,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void clearActivityStackAttributesCalculator() { + if (!Flags.activityEmbeddingOverlayPresentationFlag()) { + return; + } synchronized (mLock) { mActivityStackAttributesCalculator = null; } @@ -351,7 +372,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return mSplitAttributesCalculator; } - @Override + // TODO(b/295993745): remove after we migrate to the bundle approach. @NonNull public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options, @NonNull IBinder token) { @@ -368,6 +389,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Registers the split organizer callback to notify about changes to active splits. + * * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with * {@link WindowExtensionsImpl#getVendorApiLevel()} 2. */ @@ -381,12 +403,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Registers the split organizer callback to notify about changes to active splits. + * * @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2 */ + @Override public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) { synchronized (mLock) { - mEmbeddingCallback = callback; - updateCallbackIfNecessary(); + mSplitInfoCallback = callback; + updateSplitInfoCallbackIfNecessary(); } } @@ -396,7 +420,35 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void clearSplitInfoCallback() { synchronized (mLock) { - mEmbeddingCallback = null; + mSplitInfoCallback = null; + } + } + + /** + * Registers the callback for the {@link ActivityStack} state change. + * + * @param executor The executor to dispatch the callback. + * @param callback The callback for this {@link ActivityStack} state change. + */ + @Override + public void registerActivityStackCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<List<ActivityStack>> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + synchronized (mLock) { + mActivityStackCallbacks.put(callback, executor); + updateActivityStackCallbackIfNecessary(); + } + } + + /** @see #registerActivityStackCallback(Executor, Consumer) */ + @Override + public void unregisterActivityStackCallback(@NonNull Consumer<List<ActivityStack>> callback) { + Objects.requireNonNull(callback); + + synchronized (mLock) { + mActivityStackCallbacks.remove(callback); } } @@ -408,13 +460,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { // Translate ActivityStack to TaskFragmentContainer. final List<TaskFragmentContainer> pendingFinishingContainers = - activityStackTokens.stream() - .map(token -> { + activityStackTokens.stream().map(token -> { synchronized (mLock) { return getContainer(token); } - }).filter(Objects::nonNull) - .toList(); + }).filter(Objects::nonNull).toList(); if (pendingFinishingContainers.isEmpty()) { return; @@ -497,6 +547,68 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + @Override + public void updateActivityStackAttributes(@NonNull IBinder activityStackToken, + @NonNull ActivityStackAttributes attributes) { + if (!Flags.activityEmbeddingOverlayPresentationFlag()) { + return; + } + Objects.requireNonNull(activityStackToken); + Objects.requireNonNull(attributes); + + synchronized (mLock) { + final TaskFragmentContainer container = getContainer(activityStackToken); + if (container == null) { + Log.w(TAG, "Cannot find TaskFragmentContainer for token:" + activityStackToken); + return; + } + if (!container.isOverlay()) { + Log.w(TAG, "Updating non-overlay container has not supported yet!"); + return; + } + + final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + final WindowContainerTransaction wct = transactionRecord.getTransaction(); + mPresenter.applyActivityStackAttributes(wct, container, attributes); + transactionRecord.apply(false /* shouldApplyIndependently */); + } + } + + @Override + @Nullable + public ParentContainerInfo getParentContainerInfo(@NonNull IBinder activityStackToken) { + if (!Flags.activityEmbeddingOverlayPresentationFlag()) { + return null; + } + Objects.requireNonNull(activityStackToken); + synchronized (mLock) { + final TaskFragmentContainer container = getContainer(activityStackToken); + if (container == null) { + return null; + } + final TaskContainer.TaskProperties properties = container.getTaskContainer() + .getTaskProperties(); + return mPresenter.createParentContainerInfoFromTaskProperties(properties); + } + } + + @Override + @Nullable + public IBinder getActivityStackToken(@NonNull String tag) { + if (!Flags.activityEmbeddingOverlayPresentationFlag()) { + return null; + } + Objects.requireNonNull(tag); + synchronized (mLock) { + final TaskFragmentContainer taskFragmentContainer = + getContainer(container -> tag.equals(container.getOverlayTag())); + if (taskFragmentContainer == null) { + return null; + } + return taskFragmentContainer.getTaskFragmentToken(); + } + } + /** * Called when the transaction is ready so that the organizer can update the TaskFragments based * on the changes in transaction. @@ -565,8 +677,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Called when a TaskFragment is created and organized by this organizer. * - * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. - * @param taskFragmentInfo Info of the TaskFragment that is created. + * @param wct The {@link WindowContainerTransaction} to make any changes with if + * needed. + * @param taskFragmentInfo Info of the TaskFragment that is created. */ // Suppress GuardedBy warning because lint ask to mark this method as // @GuardedBy(container.mController.mLock), which is mLock itself @@ -574,7 +687,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, - @NonNull TaskFragmentInfo taskFragmentInfo) { + @NonNull TaskFragmentInfo taskFragmentInfo) { final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; @@ -594,8 +707,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Called when the status of an organized TaskFragment is changed. * - * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. - * @param taskFragmentInfo Info of the TaskFragment that is changed. + * @param wct The {@link WindowContainerTransaction} to make any changes with if + * needed. + * @param taskFragmentInfo Info of the TaskFragment that is changed. */ // Suppress GuardedBy warning because lint ask to mark this method as // @GuardedBy(container.mController.mLock), which is mLock itself @@ -665,8 +779,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Called when an organized TaskFragment is removed. * - * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. - * @param taskFragmentInfo Info of the TaskFragment that is removed. + * @param wct The {@link WindowContainerTransaction} to make any changes with if + * needed. + * @param taskFragmentInfo Info of the TaskFragment that is removed. */ @VisibleForTesting @GuardedBy("mLock") @@ -686,14 +801,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Called when the parent leaf Task of organized TaskFragments is changed. * When the leaf Task is changed, the organizer may want to update the TaskFragments in one * transaction. - * + * <p> * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged} * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there * can be an override bounds. * - * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. - * @param taskId Id of the parent Task that is changed. - * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task. + * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. + * @param taskId Id of the parent Task that is changed. + * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task. */ @VisibleForTesting @GuardedBy("mLock") @@ -746,20 +861,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * original Task. In this case, we need to notify the organizer so that it can check if the * Activity matches any split rule. * - * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. - * @param taskId The Task that the activity is reparented to. - * @param activityIntent The intent that the activity is original launched with. - * @param activityToken If the activity belongs to the same process as the organizer, this - * will be the actual activity token; if the activity belongs to a - * different process, the server will generate a temporary token that - * the organizer can use to reparent the activity through - * {@link WindowContainerTransaction} if needed. + * @param wct The {@link WindowContainerTransaction} to make any changes with if + * needed. + * @param taskId The Task that the activity is reparented to. + * @param activityIntent The intent that the activity is original launched with. + * @param activityToken If the activity belongs to the same process as the organizer, this + * will be the actual activity token; if the activity belongs to a + * different process, the server will generate a temporary token that + * the organizer can use to reparent the activity through + * {@link WindowContainerTransaction} if needed. */ @VisibleForTesting @GuardedBy("mLock") void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, - int taskId, @NonNull Intent activityIntent, - @NonNull IBinder activityToken) { + int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { // If the activity belongs to the current app process, we treat it as a new activity // launch. final Activity activity = getActivity(activityToken); @@ -807,14 +922,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Called when the {@link WindowContainerTransaction} created with * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. * - * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. - * @param errorCallbackToken token set in - * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} - * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no - * TaskFragment created. - * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed - * transaction operation. - * @param exception exception from the server side. + * @param wct The {@link WindowContainerTransaction} to make any changes with if + * needed. + * @param errorCallbackToken token set in + * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} + * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no + * TaskFragment created. + * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed + * transaction operation. + * @param exception exception from the server side. */ // Suppress GuardedBy warning because lint ask to mark this method as // @GuardedBy(container.mController.mLock), which is mLock itself @@ -854,7 +970,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */ + /** + * Called on receiving {@link #onTaskFragmentVanished} for cleanup. + */ @GuardedBy("mLock") private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { @@ -881,11 +999,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Checks if the new added activity should be routed to a particular container. It can create a * new container for the activity and a new split container if necessary. - * @param activity the activity that is newly added to the Task. - * @param isOnReparent whether the activity is reparented to the Task instead of new launched. - * We only support to split as primary for reparented activity for now. + * + * @param activity the activity that is newly added to the Task. + * @param isOnReparent whether the activity is reparented to the Task instead of new launched. + * We only support to split as primary for reparented activity for now. * @return {@code true} if the activity has been handled, such as placed in a TaskFragment, or - * in a state that the caller shouldn't handle. + * in a state that the caller shouldn't handle. */ @VisibleForTesting @GuardedBy("mLock") @@ -918,7 +1037,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null; if (!isOnReparent && taskContainer != null && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */) - != container) { + != container) { // Do not resolve if the launched activity is not the top-most container (excludes // the pinned and overlay container) in the Task. return true; @@ -943,6 +1062,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules. */ + @GuardedBy("mLock") boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct, @NonNull Activity activity, @Nullable TaskFragmentContainer container, boolean isOnReparent) { @@ -1027,7 +1147,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") @VisibleForTesting void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct, - @NonNull Activity activity) { + @NonNull Activity activity) { if (getContainerWithActivity(activity) != null) { // The activity has already been put in a TaskFragment. This is likely to be done by // the server when the activity is started. @@ -1077,7 +1197,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @GuardedBy("mLock") private void expandActivity(@NonNull WindowContainerTransaction wct, - @NonNull Activity activity) { + @NonNull Activity activity) { final TaskFragmentContainer container = getContainerWithActivity(activity); if (shouldContainerBeExpanded(container)) { // Make sure that the existing container is expanded. @@ -1089,7 +1209,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - /** Whether the given new launched activity is in a split with a rule matched. */ + /** + * Whether the given new launched activity is in a split with a rule matched. + */ // Suppress GuardedBy warning because lint asks to mark this method as // @GuardedBy(mPresenter.mController.mLock), which is mLock itself @SuppressWarnings("GuardedBy") @@ -1147,7 +1269,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return getSplitRule(primaryActivity, launchedActivity) != null; } - /** Finds the activity below the given activity. */ + /** + * Finds the activity below the given activity. + */ @VisibleForTesting @Nullable @GuardedBy("mLock") @@ -1198,8 +1322,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity)); if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() && canReuseContainer(splitRule, splitContainer.getSplitRule(), - taskProperties.getTaskMetrics(), - calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) { + taskProperties.getTaskMetrics(), + calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) { // Can launch in the existing secondary container if the rules share the same // presentation. final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); @@ -1333,7 +1457,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * prioritize to split the new activity with it if it is not * {@code null}. * @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there - * is no embedding rule matched. + * is no embedding rule matched. */ @VisibleForTesting @Nullable @@ -1478,7 +1602,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds(); final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds); final int windowingMode = taskContainer - .getWindowingModeForSplitTaskFragment(sanitizedBounds); + .getWindowingModeForTaskFragment(sanitizedBounds); mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(), sanitizedBounds, windowingMode); mPresenter.updateAnimationParams(wct, taskFragmentToken, @@ -1495,7 +1619,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @NonNull private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent, - @NonNull Rect taskBounds) { + @NonNull Rect taskBounds) { if (bounds.isEmpty()) { // Don't need to check if the bounds follows the task bounds. return bounds; @@ -1534,11 +1658,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen getActivityIntentMinDimensionsPair(primaryActivity, intent)); if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics, - calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes()) + calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes()) // TODO(b/231845476) we should always respect clearTop. || !respectClearTop) && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, - null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) { + null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) { // Can launch in the existing secondary container if the rules share the same // presentation. return splitContainer.getSecondaryContainer(); @@ -1563,29 +1687,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) { // Check pending appeared activity first because there can be a delay for the server // update. - for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) - .getTaskFragmentContainers(); - for (int j = containers.size() - 1; j >= 0; j--) { - final TaskFragmentContainer container = containers.get(j); - if (container.hasPendingAppearedActivity(activityToken)) { - return container; - } - } + TaskFragmentContainer taskFragmentContainer = + getContainer(container -> container.hasPendingAppearedActivity(activityToken)); + if (taskFragmentContainer != null) { + return taskFragmentContainer; } + // Check appeared activity if there is no such pending appeared activity. - for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) - .getTaskFragmentContainers(); - for (int j = containers.size() - 1; j >= 0; j--) { - final TaskFragmentContainer container = containers.get(j); - if (container.hasAppearedActivity(activityToken)) { - return container; - } - } - } - return null; + return getContainer(container -> container.hasAppearedActivity(activityToken)); } @GuardedBy("mLock") @@ -1611,8 +1721,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, - @NonNull Activity activityInTask, int taskId, - @NonNull TaskFragmentContainer pairedPrimaryContainer) { + @NonNull Activity activityInTask, int taskId, + @NonNull TaskFragmentContainer pairedPrimaryContainer) { return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, activityInTask, taskId, pairedPrimaryContainer, null /* tag */, null /* launchOptions */); @@ -1622,18 +1732,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Creates and registers a new organized container with an optional activity that will be * re-parented to it in a WCT. * - * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment. - * @param pendingAppearedIntent the Intent that will be started in the TaskFragment. - * @param activityInTask activity in the same Task so that we can get the Task bounds - * if needed. - * @param taskId parent Task of the new TaskFragment. - * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is - * set, the new container will be added right above it. - * @param overlayTag The tag for the new created overlay container. It must be - * needed if {@code isOverlay} is {@code true}. Otherwise, - * it should be {@code null}. - * @param launchOptions The launch options bundle to create a container. Must be - * specified for overlay container. + * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment. + * @param pendingAppearedIntent the Intent that will be started in the TaskFragment. + * @param activityInTask activity in the same Task so that we can get the Task bounds + * if needed. + * @param taskId parent Task of the new TaskFragment. + * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is + * set, the new container will be added right above it. + * @param overlayTag The tag for the new created overlay container. It must be + * needed if {@code isOverlay} is {@code true}. Otherwise, + * it should be {@code null}. + * @param launchOptions The launch options bundle to create a container. Must be + * specified for overlay container. */ @GuardedBy("mLock") TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, @@ -1674,7 +1784,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen primaryContainer.getTaskContainer().addSplitContainer(splitContainer); } - /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ + /** + * Cleanups all the dependencies when the TaskFragment is entering PIP. + */ @GuardedBy("mLock") private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { @@ -1833,16 +1945,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @SuppressWarnings("GuardedBy") @GuardedBy("mLock") void updateOverlayContainer(@NonNull WindowContainerTransaction wct, - @NonNull TaskFragmentContainer container) { + @NonNull TaskFragmentContainer container) { final TaskContainer taskContainer = container.getTaskContainer(); // Dismiss the overlay container if it's the only container in the task and there's no // direct activity in the parent task. if (taskContainer.getTaskFragmentContainers().size() == 1 && !taskContainer.hasDirectActivity()) { container.finish(false /* shouldFinishDependent */, mPresenter, wct, this); + return; } - // TODO(b/295805054): Add the logic to update overlay container + if (mActivityStackAttributesCalculator != null) { + final ActivityStackAttributesCalculatorParams params = + new ActivityStackAttributesCalculatorParams( + mPresenter.createParentContainerInfoFromTaskProperties( + taskContainer.getTaskProperties()), + container.getOverlayTag(), + container.getLaunchOptions()); + final ActivityStackAttributes attributes = mActivityStackAttributesCalculator + .apply(params); + mPresenter.applyActivityStackAttributes(wct, container, attributes); + } } /** @@ -1851,11 +1974,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * are {@code null}, the {@link SplitAttributes} will be calculated with * {@link SplitPresenter#computeSplitAttributes}. * - * @param splitContainer The {@link SplitContainer} to update + * @param splitContainer The {@link SplitContainer} to update * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}. * Otherwise, use the value calculated by * {@link SplitPresenter#computeSplitAttributes} - * * @return {@code true} if the update succeed. Otherwise, returns {@code false}. */ @VisibleForTesting @@ -1890,7 +2012,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } - /** Whether the given split is the topmost split in the Task. */ + /** + * Whether the given split is the topmost split in the Task. + */ private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) { final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer() .getTaskContainer().getSplitContainers(); @@ -1997,7 +2121,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } - /** Whether or not to allow activity in this container to launch placeholder. */ + /** + * Whether or not to allow activity in this container to launch placeholder. + */ @GuardedBy("mLock") private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) { final TaskFragmentContainer topContainer = container.getTaskContainer() @@ -2031,8 +2157,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Gets the activity options for starting the placeholder activity. In case the placeholder is * launched when the Task is in the background, we don't want to bring the Task to the front. - * @param primaryActivity the primary activity to launch the placeholder from. - * @param isOnCreated whether this happens during the primary activity onCreated. + * + * @param primaryActivity the primary activity to launch the placeholder from. + * @param isOnCreated whether this happens during the primary activity onCreated. */ @VisibleForTesting @GuardedBy("mLock") @@ -2104,7 +2231,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @VisibleForTesting @GuardedBy("mLock") void updateCallbackIfNecessary() { - if (mEmbeddingCallback == null || !readyToReportToClient()) { + updateSplitInfoCallbackIfNecessary(); + updateActivityStackCallbackIfNecessary(); + } + + /** + * Notifies callbacks about changes to split states if necessary. + */ + @GuardedBy("mLock") + private void updateSplitInfoCallbackIfNecessary() { + if (!readyToReportToClient() || mSplitInfoCallback == null) { return; } final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable(); @@ -2113,7 +2249,32 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } mLastReportedSplitStates.clear(); mLastReportedSplitStates.addAll(currentSplitStates); - mEmbeddingCallback.accept(currentSplitStates); + mSplitInfoCallback.accept(currentSplitStates); + } + + /** + * Notifies callbacks about changes to {@link ActivityStack} states if necessary. + */ + @GuardedBy("mLock") + private void updateActivityStackCallbackIfNecessary() { + if (!readyToReportToClient() || mActivityStackCallbacks.isEmpty()) { + return; + } + final List<ActivityStack> currentActivityStacks = getActivityStacksIfStable(); + if (currentActivityStacks == null + || mLastReportedActivityStacks.equals(currentActivityStacks)) { + return; + } + mLastReportedActivityStacks.clear(); + mLastReportedActivityStacks.addAll(currentActivityStacks); + // Copy the map in case a callback is removed during the for-loop. + final ArrayMap<Consumer<List<ActivityStack>>, Executor> callbacks = + new ArrayMap<>(mActivityStackCallbacks); + for (int i = callbacks.size() - 1; i >= 0; --i) { + final Executor executor = callbacks.valueAt(i); + final Consumer<List<ActivityStack>> callback = callbacks.keyAt(i); + executor.execute(() -> callback.accept(currentActivityStacks)); + } } /** @@ -2138,6 +2299,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** + * Returns a list of currently active {@link ActivityStack activityStacks}. + * + * @return a list of {@link ActivityStack activityStacks} if all the containers are in + * a stable state, or {@code null} otherwise. + */ + @GuardedBy("mLock") + @Nullable + private List<ActivityStack> getActivityStacksIfStable() { + final List<ActivityStack> activityStacks = new ArrayList<>(); + for (int i = mTaskContainers.size() - 1; i >= 0; i--) { + final List<ActivityStack> taskActivityStacks = + mTaskContainers.valueAt(i).getActivityStacksIfStable(); + if (taskActivityStacks == null) { + return null; + } + activityStacks.addAll(taskActivityStacks); + } + return activityStacks; + } + + /** * Whether we can now report the split states to the client. */ @GuardedBy("mLock") @@ -2207,11 +2389,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Nullable @GuardedBy("mLock") TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) { + return getContainer(container -> fragmentToken.equals(container.getTaskFragmentToken())); + } + + @Nullable + @GuardedBy("mLock") + TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) .getTaskFragmentContainers(); - for (TaskFragmentContainer container : containers) { - if (container.getTaskFragmentToken().equals(fragmentToken)) { + for (int j = containers.size() - 1; j >= 0; j--) { + final TaskFragmentContainer container = containers.get(j); + if (predicate.test(container)) { return container; } } @@ -2304,6 +2493,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * container. There is a case when primary containers for placeholders should be retained * despite the rule configuration to finish primary with secondary - if they are marked as * 'sticky' and the placeholder was finished when fully overlapping the primary container. + * * @return {@code true} if the associated container should be retained (and not be finished). */ // Suppress GuardedBy warning because lint ask to mark this method as @@ -2388,8 +2578,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions // specified by Intent, expand the overlay container to fill the parent task instead. final ActivityStackAttributesCalculatorParams params = - new ActivityStackAttributesCalculatorParams(mPresenter.toParentContainerInfo( - mPresenter.getTaskProperties(launchActivity)), overlayTag, options); + new ActivityStackAttributesCalculatorParams( + mPresenter.createParentContainerInfoFromTaskProperties( + mPresenter.getTaskProperties(launchActivity)), overlayTag, options); // Fallback to expand the bounds if there's no activityStackAttributes calculator. final Rect relativeBounds = mActivityStackAttributesCalculator != null ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds()) @@ -2407,26 +2598,31 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen && taskId == overlayContainer.getTaskId()) { // If there's an overlay container with different tag shown in the same // task, dismiss the existing overlay container. - overlayContainer.finish(false /* shouldFinishDependant */, mPresenter, - wct, SplitController.this); + mPresenter.cleanupContainer(wct, overlayContainer, + false /* shouldFinishDependant */); } if (overlayTag.equals(overlayContainer.getOverlayTag()) && taskId != overlayContainer.getTaskId()) { // If there's an overlay container with same tag in a different task, // dismiss the overlay container since the tag must be unique per process. - overlayContainer.finish(false /* shouldFinishDependant */, mPresenter, - wct, SplitController.this); + mPresenter.cleanupContainer(wct, overlayContainer, + false /* shouldFinishDependant */); } if (overlayTag.equals(overlayContainer.getOverlayTag()) && taskId == overlayContainer.getTaskId()) { // If there's an overlay container with the same tag and task ID, we treat // the OverlayCreateParams as the update to the container. - final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties() - .getTaskMetrics().getBounds(); final IBinder overlayToken = overlayContainer.getTaskFragmentToken(); + final TaskContainer taskContainer = overlayContainer.getTaskContainer(); + final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics() + .getBounds(); final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds); + mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds); - mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken, + final int windowingMode = taskContainer + .getWindowingModeForTaskFragment(sanitizedBounds); + mPresenter.updateWindowingMode(wct, overlayToken, windowingMode); + mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayContainer, !sanitizedBounds.isEmpty()); // We can just return the updated overlay container and don't need to // check other condition since we only have one OverlayCreateParams, and @@ -2539,7 +2735,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - /** Executor that posts on the main application thread. */ + /** + * Executor that posts on the main application thread. + */ private static class MainThreadExecutor implements Executor { private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -2687,7 +2885,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen && calculatedSplitAttributes.equals(containerSplitAttributes); } - /** Whether the two rules have the same presentation. */ + /** + * Whether the two rules have the same presentation. + */ @VisibleForTesting static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1, @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index acfd8e4314bf..543570c63ad7 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -16,6 +16,8 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.PackageManager.MATCH_ALL; import android.app.Activity; @@ -187,7 +189,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes); final int windowingMode = mController.getTaskContainer(taskId) - .getWindowingModeForSplitTaskFragment(secondaryRelBounds); + .getWindowingModeForTaskFragment(secondaryRelBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), secondaryRelBounds, windowingMode); updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes); @@ -259,7 +261,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (container == null || container == containerToAvoid) { container = mController.newContainer(activity, taskId); final int windowingMode = mController.getTaskContainer(taskId) - .getWindowingModeForSplitTaskFragment(relBounds); + .getWindowingModeForTaskFragment(relBounds); final IBinder reparentActivityToken = activity.getActivityToken(); createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken, relBounds, windowingMode, reparentActivityToken); @@ -268,7 +270,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } else { resizeTaskFragmentIfRegistered(wct, container, relBounds); final int windowingMode = mController.getTaskContainer(taskId) - .getWindowingModeForSplitTaskFragment(relBounds); + .getWindowingModeForTaskFragment(relBounds); updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); } updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes); @@ -310,7 +312,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Pass in the primary container to make sure it is added right above the primary. primaryContainer); final TaskContainer taskContainer = mController.getTaskContainer(taskId); - final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( + final int windowingMode = taskContainer.getWindowingModeForTaskFragment( primaryRelBounds); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule, splitAttributes); @@ -347,6 +349,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { && secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */) && !secondaryRelBounds.isEmpty(); + // TODO(b/243518738): remove usages of XXXIfRegistered. // If the task fragments are not registered yet, the positions will be updated after they // are created again. resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRelBounds); @@ -357,7 +360,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); } - final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment( + final int windowingMode = taskContainer.getWindowingModeForTaskFragment( primaryRelBounds); updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode); updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); @@ -398,13 +401,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer} */ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct, - @NonNull TaskFragmentContainer taskFragmentContainer, + @NonNull TaskFragmentContainer container, boolean isolatedNavigationEnabled) { - if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) { + if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) { return; } - taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled); - setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(), + container.setIsolatedNavigationEnabled(isolatedNavigationEnabled); + setTaskFragmentIsolatedNavigation(wct, container.getTaskFragmentToken(), isolatedNavigationEnabled); } @@ -566,6 +569,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { super.setCompanionTaskFragment(wct, primary, secondary); } + void applyActivityStackAttributes(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container, @NonNull ActivityStackAttributes attributes) { + final Rect bounds = attributes.getRelativeBounds(); + + resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds); + updateWindowingMode(wct, container.getTaskFragmentToken(), + bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW); + } + /** * Expands the split container if the current split bounds are smaller than the Activity or * Intent that is added to the container. @@ -1086,7 +1098,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @NonNull - ParentContainerInfo toParentContainerInfo(@NonNull TaskProperties taskProperties) { + ParentContainerInfo createParentContainerInfoFromTaskProperties( + @NonNull TaskProperties taskProperties) { final Configuration configuration = taskProperties.getConfiguration(); final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(), diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 028e75fe010f..64ad4faa421d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -145,7 +145,7 @@ class TaskContainer { * the pair of TaskFragments are stacked due to the limited space. */ @WindowingMode - int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) { + int getWindowingModeForTaskFragment(@Nullable Rect taskFragmentBounds) { // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it // will be set to UNDEFINED which will then inherit the Task windowing mode. if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) { @@ -443,6 +443,26 @@ class TaskContainer { return splitStates; } + // TODO(b/317358445): Makes ActivityStack and SplitInfo callback more stable. + /** + * Returns a list of currently active {@link ActivityStack activityStacks}. + * + * @return a list of {@link ActivityStack activityStacks} if all the containers are in + * a stable state, or {@code null} otherwise. + */ + @Nullable + List<ActivityStack> getActivityStacksIfStable() { + final List<ActivityStack> activityStacks = new ArrayList<>(); + for (TaskFragmentContainer container : mContainers) { + final ActivityStack activityStack = container.toActivityStackIfStable(); + if (activityStack == null) { + return null; + } + activityStacks.add(activityStack); + } + return activityStacks; + } + /** A wrapper class which contains the information of {@link TaskContainer} */ static final class TaskProperties { private final int mDisplayId; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index afd554b6e52b..810bded8a7f0 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -107,11 +107,11 @@ class TaskFragmentContainer { private final String mOverlayTag; /** - * The launch options that was used to create this container. Must not be {@code null} for - * {@link #isOverlay()} container. + * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()} + * for {@link #isOverlay()} container. */ - @Nullable - private final Bundle mLaunchOptions; + @NonNull + private final Bundle mLaunchOptions = new Bundle(); /** Indicates whether the container was cleaned up after the last activity was removed. */ private boolean mIsFinished; @@ -210,7 +210,9 @@ class TaskFragmentContainer { if (overlayTag != null) { Objects.requireNonNull(launchOptions); } - mLaunchOptions = launchOptions; + if (launchOptions != null) { + mLaunchOptions.putAll(launchOptions); + } if (pairedPrimaryContainer != null) { // The TaskFragment will be positioned right above the paired container. @@ -925,6 +927,17 @@ class TaskFragmentContainer { return mOverlayTag; } + /** + * Returns the options that was used to launch this {@link TaskFragmentContainer}. + * {@link Bundle#isEmpty()} means there's no launch option for this container. + * <p> + * Note that WM Jetpack owns the logic. The WM Extension library must not modify this object. + */ + @NonNull + Bundle getLaunchOptions() { + return mLaunchOptions; + } + @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 678bdef3df92..5ef6a5263f96 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -16,6 +16,7 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG; @@ -287,10 +288,10 @@ public class OverlayPresentationTest { createOrUpdateOverlayTaskFragmentIfNeeded("test"); verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect()); - verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken, + verify(mSplitPresenter).updateWindowingMode(mTransaction, overlayToken, + WINDOWING_MODE_UNDEFINED); + verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayContainer, false); - assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) - .containsExactly(overlayContainer); } @Test @@ -315,8 +316,10 @@ public class OverlayPresentationTest { createOrUpdateOverlayTaskFragmentIfNeeded("test"); verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect()); - verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken, - false); + verify(mSplitPresenter).updateWindowingMode(mTransaction, + overlayToken, WINDOWING_MODE_UNDEFINED); + verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, + overlayContainer, false); assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) .containsExactly(overlayContainer); } @@ -425,6 +428,50 @@ public class OverlayPresentationTest { .that(taskContainer.getTaskFragmentContainers()).isEmpty(); } + @Test + public void testUpdateActivityStackAttributes_nullParams_throwException() { + assertThrows(NullPointerException.class, () -> + mSplitController.updateActivityStackAttributes(null, + new ActivityStackAttributes.Builder().build())); + + assertThrows(NullPointerException.class, () -> + mSplitController.updateActivityStackAttributes(new Binder(), null)); + + verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any()); + } + + @Test + public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() { + final TaskFragmentContainer container = mSplitController.newContainer(mActivity, + mActivity.getTaskId()); + mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(), + new ActivityStackAttributes.Builder().build()); + + verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any()); + } + + @Test + public void testUpdateActivityStackAttributes_notOverlay_earlyReturn() { + final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); + + mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(), + new ActivityStackAttributes.Builder().build()); + + verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any()); + } + + @Test + public void testUpdateActivityStackAttributes() { + final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test"); + doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any()); + final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build(); + final IBinder token = container.getTaskFragmentToken(); + + mSplitController.updateActivityStackAttributes(token, attrs); + + verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs)); + } + /** * A simplified version of {@link SplitController.ActivityStartMonitor * #createOrUpdateOverlayTaskFragmentIfNeeded} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index bab4e9195880..b60943a60076 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -354,7 +354,7 @@ public class SplitControllerTest { bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, container.getTaskFragmentToken()); monitor.mCurrentIntent = intent; - doReturn(container).when(mSplitController).getContainer(any()); + doReturn(container).when(mSplitController).getContainer(any(IBinder.class)); monitor.onStartActivityResult(START_CANCELED, bundle); assertNull(container.getPendingAppearedIntent()); @@ -1642,7 +1642,7 @@ public class SplitControllerTest { // We need to set those in case we are not respecting clear top. // TODO(b/231845476) we should always respect clearTop. final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId()) - .getWindowingModeForSplitTaskFragment(TASK_BOUNDS); + .getWindowingModeForTaskFragment(TASK_BOUNDS); primaryContainer.setLastRequestedWindowingMode(windowingMode); secondaryContainer.setLastRequestedWindowingMode(windowingMode); primaryContainer.setLastRequestedBounds(getSplitBounds(true /* isPrimary */)); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index 7b77235f66f7..a5995a3027ac 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -75,7 +75,7 @@ public class TaskContainerTest { final Configuration configuration = new Configuration(); assertEquals(WINDOWING_MODE_MULTI_WINDOW, - taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + taskContainer.getWindowingModeForTaskFragment(splitBounds)); configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, @@ -83,7 +83,7 @@ public class TaskContainerTest { null /* decorSurface */)); assertEquals(WINDOWING_MODE_MULTI_WINDOW, - taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + taskContainer.getWindowingModeForTaskFragment(splitBounds)); configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, @@ -91,12 +91,12 @@ public class TaskContainerTest { null /* decorSurface */)); assertEquals(WINDOWING_MODE_FREEFORM, - taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); + taskContainer.getWindowingModeForTaskFragment(splitBounds)); // Empty bounds means the split pair are stacked, so it should be UNDEFINED which will then // inherit the Task windowing mode assertEquals(WINDOWING_MODE_UNDEFINED, - taskContainer.getWindowingModeForSplitTaskFragment(new Rect())); + taskContainer.getWindowingModeForTaskFragment(new Rect())); } @Test diff --git a/libs/incident/libincident.map.txt b/libs/incident/libincident.map.txt index f75cceaf59fa..d8650e129742 100644 --- a/libs/incident/libincident.map.txt +++ b/libs/incident/libincident.map.txt @@ -1,15 +1,15 @@ LIBINCIDENT { global: - AIncidentReportArgs_init; # systemapi # introduced=30 - AIncidentReportArgs_clone; # systemapi # introduced=30 - AIncidentReportArgs_delete; # systemapi # introduced=30 - AIncidentReportArgs_setAll; # systemapi # introduced=30 - AIncidentReportArgs_setPrivacyPolicy; # systemapi # introduced=30 - AIncidentReportArgs_addSection; # systemapi # introduced=30 - AIncidentReportArgs_setReceiverPackage; # systemapi # introduced=30 - AIncidentReportArgs_setReceiverClass; # systemapi # introduced=30 - AIncidentReportArgs_addHeader; # systemapi # introduced=30 - AIncidentReportArgs_takeReport; # systemapi # introduced=30 + AIncidentReportArgs_init; # systemapi introduced=30 + AIncidentReportArgs_clone; # systemapi introduced=30 + AIncidentReportArgs_delete; # systemapi introduced=30 + AIncidentReportArgs_setAll; # systemapi introduced=30 + AIncidentReportArgs_setPrivacyPolicy; # systemapi introduced=30 + AIncidentReportArgs_addSection; # systemapi introduced=30 + AIncidentReportArgs_setReceiverPackage; # systemapi introduced=30 + AIncidentReportArgs_setReceiverClass; # systemapi introduced=30 + AIncidentReportArgs_addHeader; # systemapi introduced=30 + AIncidentReportArgs_takeReport; # systemapi introduced=30 local: *; }; diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING index 8f5f1f6a4794..4fbe9ee90c4c 100644 --- a/media/TEST_MAPPING +++ b/media/TEST_MAPPING @@ -48,9 +48,7 @@ {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] - } - ], - "postsubmit": [ + }, { "file_patterns": [ "[^/]*(LoudnessCodec)[^/]*\\.java" diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java index 3b15632d065d..ce1004c4c58c 100644 --- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java +++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java @@ -95,12 +95,17 @@ public class LoudnessCodecConfiguratorTest { @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) public void setAudioTrack_callsAudioServiceStart() throws Exception { final AudioTrack track = createAudioTrack(); + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - mLcc.addMediaCodec(createAndConfigureMediaCodec()); - mLcc.setAudioTrack(track); + try { + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), - anyList()); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), + anyList()); + } finally { + mediaCodec.release(); + } } @Test @@ -108,10 +113,15 @@ public class LoudnessCodecConfiguratorTest { public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception { when(mAudioService.getLoudnessParams(anyInt(), any())).thenReturn(new PersistableBundle()); final AudioTrack track = createAudioTrack(); + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - mLcc.getLoudnessCodecParams(track, createAndConfigureMediaCodec()); + try { + mLcc.getLoudnessCodecParams(track, mediaCodec); - verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any()); + verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any()); + } finally { + mediaCodec.release(); + } } @Test @@ -120,10 +130,14 @@ public class LoudnessCodecConfiguratorTest { final AudioTrack track = createAudioTrack(); final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); + try { + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + } finally { + mediaCodec.release(); + } } @Test @@ -132,24 +146,33 @@ public class LoudnessCodecConfiguratorTest { final AudioTrack track = createAudioTrack(); final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); - mLcc.setAudioTrack(track); + try { + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); + mLcc.setAudioTrack(track); - verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()), - anyList()); + verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()), + anyList()); + } finally { + mediaCodec.release(); + } } @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) public void setTrackNull_stopCodecUpdates() throws Exception { final AudioTrack track = createAudioTrack(); + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - mLcc.addMediaCodec(createAndConfigureMediaCodec()); - mLcc.setAudioTrack(track); + try { + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); - mLcc.setAudioTrack(null); // stops updates - verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId())); + mLcc.setAudioTrack(null); // stops updates + verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId())); + } finally { + mediaCodec.release(); + } } @Test @@ -157,27 +180,37 @@ public class LoudnessCodecConfiguratorTest { public void addMediaCodecTwice_triggersIAE() throws Exception { final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - mLcc.addMediaCodec(mediaCodec); + try { + mLcc.addMediaCodec(mediaCodec); - assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec)); + assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec)); + } finally { + mediaCodec.release(); + } } @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception { final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class); - final AudioTrack track = createAudioTrack(); - - mLcc.addMediaCodec(createAndConfigureMediaCodec()); - mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), - argument.capture()); - assertEquals(argument.getValue().size(), 1); - - mLcc.addMediaCodec(createAndConfigureMediaCodec()); - mLcc.setAudioTrack(null); - verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId())); + final MediaCodec mediaCodec1 = createAndConfigureMediaCodec(); + final MediaCodec mediaCodec2 = createAndConfigureMediaCodec(); + + try { + mLcc.addMediaCodec(mediaCodec1); + mLcc.setAudioTrack(track); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), + argument.capture()); + assertEquals(argument.getValue().size(), 1); + + mLcc.addMediaCodec(mediaCodec2); + mLcc.setAudioTrack(null); + verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId())); + } finally { + mediaCodec1.release(); + mediaCodec2.release(); + } } @Test @@ -186,24 +219,35 @@ public class LoudnessCodecConfiguratorTest { final AudioTrack track = createAudioTrack(); final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); - mLcc.removeMediaCodec(mediaCodec); + try { + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); + mLcc.removeMediaCodec(mediaCodec); - verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + } finally { + mediaCodec.release(); + } } @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception { final AudioTrack track = createAudioTrack(); - - mLcc.addMediaCodec(createAndConfigureMediaCodec()); - mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); - - mLcc.addMediaCodec(createAndConfigureMediaCodec()); - verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any()); + final MediaCodec mediaCodec1 = createAndConfigureMediaCodec(); + final MediaCodec mediaCodec2 = createAndConfigureMediaCodec(); + + try { + mLcc.addMediaCodec(mediaCodec1); + mLcc.setAudioTrack(track); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + + mLcc.addMediaCodec(mediaCodec2); + verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any()); + } finally { + mediaCodec1.release(); + mediaCodec2.release(); + } } @Test @@ -212,25 +256,36 @@ public class LoudnessCodecConfiguratorTest { final AudioTrack track = createAudioTrack(); final MediaCodec mediaCodec = createAndConfigureMediaCodec(); - mLcc.addMediaCodec(mediaCodec); - mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + try { + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); - mLcc.removeMediaCodec(mediaCodec); - verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + mLcc.removeMediaCodec(mediaCodec); + verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + } finally { + mediaCodec.release(); + } } @Test @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) public void removeWrongMediaCodecAfterSetTrack_triggersIAE() throws Exception { final AudioTrack track = createAudioTrack(); - - mLcc.addMediaCodec(createAndConfigureMediaCodec()); - mLcc.setAudioTrack(track); - verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); - - assertThrows(IllegalArgumentException.class, - () -> mLcc.removeMediaCodec(createAndConfigureMediaCodec())); + final MediaCodec mediaCodec1 = createAndConfigureMediaCodec(); + final MediaCodec mediaCodec2 = createAndConfigureMediaCodec(); + + try { + mLcc.addMediaCodec(mediaCodec1); + mLcc.setAudioTrack(track); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + + assertThrows(IllegalArgumentException.class, + () -> mLcc.removeMediaCodec(mediaCodec2)); + } finally { + mediaCodec1.release(); + mediaCodec2.release(); + } } private static AudioTrack createAudioTrack() { @@ -250,19 +305,21 @@ public class LoudnessCodecConfiguratorTest { MediaExtractor extractor; extractor = new MediaExtractor(); - extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(), + try { + extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(), testFd.getLength()); - testFd.close(); - - assertEquals("wrong number of tracks", 1, extractor.getTrackCount()); - MediaFormat format = extractor.getTrackFormat(0); - String mime = format.getString(MediaFormat.KEY_MIME); - assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX)); - final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime); - - Log.v(TAG, "configuring with " + format); - mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */); - - return mediaCodec; + assertEquals("wrong number of tracks", 1, extractor.getTrackCount()); + MediaFormat format = extractor.getTrackFormat(0); + String mime = format.getString(MediaFormat.KEY_MIME); + assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX)); + final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime); + + Log.v(TAG, "configuring with " + format); + mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */); + return mediaCodec; + } finally { + testFd.close(); + extractor.release(); + } } } diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 9f2a9ac4798d..960510879a4c 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -1,9 +1,9 @@ LIBANDROID { global: - AActivityManager_addUidImportanceListener; # systemapi # introduced=31 - AActivityManager_removeUidImportanceListener; # systemapi # introduced=31 - AActivityManager_isUidActive; # systemapi # introduced=31 - AActivityManager_getUidImportance; # systemapi # introduced=31 + AActivityManager_addUidImportanceListener; # systemapi introduced=31 + AActivityManager_removeUidImportanceListener; # systemapi introduced=31 + AActivityManager_isUidActive; # systemapi introduced=31 + AActivityManager_getUidImportance; # systemapi introduced=31 AAssetDir_close; AAssetDir_getNextFileName; AAssetDir_rewind; diff --git a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml new file mode 100644 index 000000000000..9d16f32db932 --- /dev/null +++ b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2023 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. + --> + +<!-- Copied from //frameworks/base/core/res/res/drawable/item_background_material.xml --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/autofill_light_colorControlHighlight"> + <item android:id="@android:id/mask"> + <color android:color="@android:color/white"/> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml new file mode 100644 index 000000000000..2f0c83b6556a --- /dev/null +++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi" + android:color="@android:color/transparent"> + <item + android:bottom="1dp" + android:shape="rectangle" + android:top="1dp"> + <shape> + <corners android:radius="28dp" /> + <solid android:color="@android:color/system_surface_container_high_light" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml new file mode 100644 index 000000000000..39f49caa7051 --- /dev/null +++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi" + android:color="@android:color/transparent"> + <item + android:bottom="1dp" + android:shape="rectangle" + android:top="1dp"> + <shape> + <corners android:radius="28dp" /> + <solid android:color="@android:color/system_surface_container_high_dark" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml new file mode 100644 index 000000000000..e4e9f7ac85a9 --- /dev/null +++ b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml @@ -0,0 +1,37 @@ +<!-- + ~ Copyright (C) 2023 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. + --> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/autofill.Dataset"> + <ImageView + android:id="@android:id/icon1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_alignParentStart="true" + android:background="@null"/> + <TextView + android:id="@android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_toEndOf="@android:id/icon1" + style="@style/autofill.TextAppearance"/> + +</RelativeLayout> diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml new file mode 100644 index 000000000000..63b9f24d9033 --- /dev/null +++ b/packages/CredentialManager/res/values/colors.xml @@ -0,0 +1,38 @@ +<!-- + ~ Copyright (C) 2023 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. + --> + +<!-- Color palette --> +<resources> + <color name="autofill_light_colorPrimary">@color/primary_material_light</color> + <color name="autofill_light_colorAccent">@color/accent_material_light</color> + <color name="autofill_light_colorControlHighlight">@color/ripple_material_light</color> + <color name="autofill_light_colorButtonNormal">@color/button_material_light</color> + + <!-- Text colors --> + <color name="autofill_light_textColorPrimary">@color/abc_primary_text_material_light</color> + <color name="autofill_light_textColorSecondary">@color/abc_secondary_text_material_light</color> + <color name="autofill_light_textColorHint">@color/abc_hint_foreground_material_light</color> + <color name="autofill_light_textColorHintInverse">@color/abc_hint_foreground_material_dark + </color> + <color name="autofill_light_textColorHighlight">@color/highlighted_text_material_light</color> + <color name="autofill_light_textColorLink">@color/autofill_light_colorAccent</color> + + <!-- These colors are used for Remote Views. --> + <color name="background_dark_mode">#0E0C0B</color> + <color name="background">#F1F3F4</color> + <color name="text_primary_dark_mode">#DFDEDB</color> + <color name="text_primary">#202124</color> +</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml new file mode 100644 index 000000000000..67003a330974 --- /dev/null +++ b/packages/CredentialManager/res/values/dimens.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <dimen name="autofill_view_padding">16dp</dimen> + <dimen name="autofill_icon_size">16dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/styles.xml b/packages/CredentialManager/res/values/styles.xml new file mode 100644 index 000000000000..4a5761acdd83 --- /dev/null +++ b/packages/CredentialManager/res/values/styles.xml @@ -0,0 +1,38 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <style name="autofill.TextAppearance.Small" parent="@style/autofill.TextAppearance"> + <item name="android:textSize">12sp</item> + </style> + + + <style name="autofill.Dataset" parent=""> + <item name="android:background">@drawable/autofill_light_selectable_item_background</item> + </style> + + <style name="autofill.TextAppearance" parent=""> + <item name="android:textColor">@color/autofill_light_textColorPrimary</item> + <item name="android:textColorHint">@color/autofill_light_textColorHint</item> + <item name="android:textColorHighlight">@color/autofill_light_textColorHighlight</item> + <item name="android:textColorLink">@color/autofill_light_textColorLink</item> + <item name="android:textSize">14sp</item> + </style> + + <style name="autofill.TextAppearance.Primary"> + <item name="android:textColor">@color/autofill_light_textColorPrimary</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 20d2f09ced8f..0ff1c7fd8953 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -16,6 +16,7 @@ package com.android.credentialmanager.autofill +import android.R import android.app.assist.AssistStructure import android.content.Context import android.credentials.CredentialManager @@ -41,18 +42,19 @@ import android.service.autofill.SaveRequest import android.service.credentials.CredentialProviderService import android.util.Log import android.view.autofill.AutofillId -import org.json.JSONException import android.widget.inline.InlinePresentationSpec import androidx.autofill.inline.v1.InlineSuggestionUi import androidx.credentials.provider.CustomCredentialEntry import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import com.android.credentialmanager.GetFlowUtils -import com.android.credentialmanager.model.get.CredentialEntryInfo +import com.android.credentialmanager.common.ui.RemoteViewsFactory import com.android.credentialmanager.getflow.ProviderDisplayInfo -import com.android.credentialmanager.model.get.ProviderInfo import com.android.credentialmanager.getflow.toProviderDisplayInfo import com.android.credentialmanager.ktx.credentialEntry +import com.android.credentialmanager.model.get.CredentialEntryInfo +import com.android.credentialmanager.model.get.ProviderInfo +import org.json.JSONException import org.json.JSONObject import java.util.concurrent.Executors @@ -127,9 +129,11 @@ class CredentialAutofillService : AutofillService() { is PasswordCredentialEntry -> { entryIconMap[entry.key + entry.subkey] = credentialEntry.icon } + is PublicKeyCredentialEntry -> { entryIconMap[entry.key + entry.subkey] = credentialEntry.icon } + is CustomCredentialEntry -> { entryIconMap[entry.key + entry.subkey] = credentialEntry.icon } @@ -172,11 +176,11 @@ class CredentialAutofillService : AutofillService() { } private fun processProvidersForAutofillId( - filLRequest: FillRequest, - autofillId: AutofillId, - providerList: List<ProviderInfo>, - entryIconMap: Map<String, Icon>, - fillResponseBuilder: FillResponse.Builder + filLRequest: FillRequest, + autofillId: AutofillId, + providerList: List<ProviderInfo>, + entryIconMap: Map<String, Icon>, + fillResponseBuilder: FillResponse.Builder ): Boolean { if (providerList.isEmpty()) { return false @@ -197,7 +201,7 @@ class CredentialAutofillService : AutofillService() { var i = 0 var datasetAdded = false - providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ { + providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{ val primaryEntry = it.sortedCredentialEntryList.first() val pendingIntent = primaryEntry.pendingIntent val fillInIntent = primaryEntry.fillInIntent @@ -206,37 +210,48 @@ class CredentialAutofillService : AutofillService() { Log.e(TAG, "PendingIntent was missing from the entry.") return@usernameLoop } - if (inlinePresentationSpecs == null || i >= maxItemCount) { + if (inlinePresentationSpecs == null) { + Log.i(TAG, "Inline presentation spec is null, " + + "building dropdown presentation only") + } + if (i >= maxItemCount) { Log.e(TAG, "Skipping because reached the max item count.") return@usernameLoop } - // Create inline presentation - val spec: InlinePresentationSpec - if (i < inlinePresentationSpecsCount) { - spec = inlinePresentationSpecs[i] - } else { - spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1] - } - val sliceBuilder = InlineSuggestionUi - .newContentBuilder(pendingIntent) - .setTitle(primaryEntry.userName) - val icon: Icon - if (primaryEntry.icon == null) { + val icon: Icon = if (primaryEntry.icon == null) { // The empty entry icon has non-null icon reference but null drawable reference. // If the drawable reference is null, then use the default icon. - icon = getDefaultIcon() + getDefaultIcon() } else { - icon = entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey] + entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey] ?: getDefaultIcon() } - sliceBuilder.setStartIcon(icon) - val inlinePresentation = InlinePresentation( - sliceBuilder.build().slice, spec, /* pinned= */ false) + // Create inline presentation + var inlinePresentation: InlinePresentation? = null; + if (inlinePresentationSpecs != null) { + val spec: InlinePresentationSpec + if (i < inlinePresentationSpecsCount) { + spec = inlinePresentationSpecs[i] + } else { + spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1] + } + val sliceBuilder = InlineSuggestionUi + .newContentBuilder(pendingIntent) + .setTitle(primaryEntry.userName) + sliceBuilder.setStartIcon(icon) + inlinePresentation = InlinePresentation( + sliceBuilder.build().slice, spec, /* pinned= */ false) + } + val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation( + this, icon, primaryEntry) i++ val dataSetBuilder = Dataset.Builder() val presentationBuilder = Presentations.Builder() - .setInlinePresentation(inlinePresentation) + .setMenuPresentation(dropdownPresentation) + if (inlinePresentation != null) { + presentationBuilder.setInlinePresentation(inlinePresentation) + } fillResponseBuilder.addDataset( dataSetBuilder @@ -305,7 +320,7 @@ class CredentialAutofillService : AutofillService() { ): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> { val autofillIdToCredentialEntries: MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf() - credentialEntryList.forEach entryLoop@ { credentialEntry -> + credentialEntryList.forEach entryLoop@{ credentialEntry -> val autofillId: AutofillId? = credentialEntry .fillInIntent ?.getParcelableExtra( @@ -323,8 +338,8 @@ class CredentialAutofillService : AutofillService() { } private fun copyProviderInfo( - providerInfo: ProviderInfo, - credentialList: List<CredentialEntryInfo> + providerInfo: ProviderInfo, + credentialList: List<CredentialEntryInfo> ): ProviderInfo { return ProviderInfo( providerInfo.id, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt new file mode 100644 index 000000000000..4dc7f00c1550 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 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.credentialmanager.common.ui + +import android.content.Context +import android.content.res.Configuration +import android.widget.RemoteViews +import androidx.core.content.ContextCompat +import com.android.credentialmanager.model.get.CredentialEntryInfo +import android.graphics.drawable.Icon + +class RemoteViewsFactory { + + companion object { + private const val setAdjustViewBoundsMethodName = "setAdjustViewBounds" + private const val setMaxHeightMethodName = "setMaxHeight" + private const val setBackgroundResourceMethodName = "setBackgroundResource" + + fun createDropdownPresentation( + context: Context, + icon: Icon, + credentialEntryInfo: CredentialEntryInfo + ): RemoteViews { + val padding = context.resources.getDimensionPixelSize(com.android + .credentialmanager.R.dimen.autofill_view_padding) + var layoutId: Int = com.android.credentialmanager.R.layout + .autofill_dataset_left_with_item_tag_hint + val remoteViews = RemoteViews(context.packageName, layoutId) + setRemoteViewsPaddings(remoteViews, padding) + val textColorPrimary = getTextColorPrimary(isDarkMode(context), context); + remoteViews.setTextColor(android.R.id.text1, textColorPrimary); + remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName) + + remoteViews.setImageViewIcon(android.R.id.icon1, icon); + remoteViews.setBoolean( + android.R.id.icon1, setAdjustViewBoundsMethodName, true); + remoteViews.setInt( + android.R.id.icon1, + setMaxHeightMethodName, + context.resources.getDimensionPixelSize( + com.android.credentialmanager.R.dimen.autofill_icon_size)); + val drawableId = if (isDarkMode(context)) + com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one_dark + else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one + remoteViews.setInt( + android.R.id.content, setBackgroundResourceMethodName, drawableId); + return remoteViews + } + + private fun setRemoteViewsPaddings( + remoteViews: RemoteViews, + padding: Int) { + val halfPadding = padding / 2 + remoteViews.setViewPadding( + android.R.id.text1, + halfPadding, + halfPadding, + halfPadding, + halfPadding) + } + + private fun isDarkMode(context: Context): Boolean { + val currentNightMode = context.resources.configuration.uiMode and + Configuration.UI_MODE_NIGHT_MASK + return currentNightMode == Configuration.UI_MODE_NIGHT_YES + } + + private fun getTextColorPrimary(darkMode: Boolean, context: Context): Int { + return if (darkMode) ContextCompat.getColor( + context, com.android.credentialmanager.R.color.text_primary_dark_mode) + else ContextCompat.getColor(context, com.android.credentialmanager.R.color.text_primary) + } + } +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 41d12dca869e..d9286b3463d4 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -212,6 +212,13 @@ flag { } flag { + name: "revamped_bouncer_messages" + namespace: "systemui" + description: "Change the bouncer message to be a 2-line more descriptive message" + bug: "236891644" +} + +flag { name: "rest_to_unlock" namespace: "systemui" description: "Require prolonged touch for fingerprint authentication" diff --git a/packages/SystemUI/animation/core/Android.bp b/packages/SystemUI/animation/Android.bp index 8438051e3430..8438051e3430 100644 --- a/packages/SystemUI/animation/core/Android.bp +++ b/packages/SystemUI/animation/Android.bp diff --git a/packages/SystemUI/animation/core/AndroidManifest.xml b/packages/SystemUI/animation/AndroidManifest.xml index 321cc53142c6..321cc53142c6 100644 --- a/packages/SystemUI/animation/core/AndroidManifest.xml +++ b/packages/SystemUI/animation/AndroidManifest.xml diff --git a/packages/SystemUI/animation/core/build.gradle b/packages/SystemUI/animation/build.gradle index 12637f4071df..939455fa44ac 100644 --- a/packages/SystemUI/animation/core/build.gradle +++ b/packages/SystemUI/animation/build.gradle @@ -5,8 +5,8 @@ apply plugin: 'kotlin-android' android { sourceSets { main { - java.srcDirs = ["${SYS_UI_DIR}/animation/core/src/com/android/systemui/surfaceeffects/"] - manifest.srcFile "${SYS_UI_DIR}/animation/core/AndroidManifest.xml" + java.srcDirs = ["${SYS_UI_DIR}/animation/src/com/android/systemui/surfaceeffects/"] + manifest.srcFile "${SYS_UI_DIR}/animation/AndroidManifest.xml" } } diff --git a/packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml b/packages/SystemUI/animation/res/anim/launch_dialog_enter.xml index c6b87d38f7da..c6b87d38f7da 100644 --- a/packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml +++ b/packages/SystemUI/animation/res/anim/launch_dialog_enter.xml diff --git a/packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml b/packages/SystemUI/animation/res/anim/launch_dialog_exit.xml index a0f441eaeed4..a0f441eaeed4 100644 --- a/packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml +++ b/packages/SystemUI/animation/res/anim/launch_dialog_exit.xml diff --git a/packages/SystemUI/animation/core/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml index 2d82307aca76..2d82307aca76 100644 --- a/packages/SystemUI/animation/core/res/values/ids.xml +++ b/packages/SystemUI/animation/res/values/ids.xml diff --git a/packages/SystemUI/animation/core/res/values/styles.xml b/packages/SystemUI/animation/res/values/styles.xml index 3b3f7f6128fa..3b3f7f6128fa 100644 --- a/packages/SystemUI/animation/core/res/values/styles.xml +++ b/packages/SystemUI/animation/res/values/styles.xml diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 9c46ebdc5ac8..9c46ebdc5ac8 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt index 1c9dabbb0e07..1c9dabbb0e07 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt index b879ba0b1ece..b879ba0b1ece 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 168039ed5d3d..168039ed5d3d 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt index c49a487c6766..c49a487c6766 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt index addabcc0282a..addabcc0282a 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt index 78ae4af258fc..78ae4af258fc 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index b738e2bc972b..b738e2bc972b 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt index d6eba2e7064d..d6eba2e7064d 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt index ed8e70568b48..ed8e70568b48 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt index d465962d6edf..d465962d6edf 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt index b89a8b0e0272..b89a8b0e0272 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index dc54240affc3..8dc74951d332 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -42,9 +42,9 @@ interface TypefaceVariantCache { return baseTypeface } - val axes = - FontVariationAxis.fromFontVariationSettings(fVar)?.toMutableList() - ?: mutableListOf() + val axes = FontVariationAxis.fromFontVariationSettings(fVar) + ?.toMutableList() + ?: mutableListOf() axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) } if (axes.isEmpty()) { return baseTypeface @@ -206,14 +206,12 @@ class TextAnimator( * * Here is an example of font runs: "fin. 終わり" * - * ``` * Characters : f i n . _ 終 わ り * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf --> * runStart = 0, runLength = 5 runStart = 5, runLength = 3 * Glyph IDs : 194 48 7 8 4367 1039 1002 * Glyph Index: 0 1 2 3 0 1 2 - * ``` * * In this example, the "fi" is converted into ligature form, thus the single glyph ID is * assigned for two characters, f and i. @@ -245,21 +243,22 @@ class TextAnimator( /** * Set text style with animation. * - * By passing -1 to weight, the view preserve the current weight. By passing -1 to textSize, the - * view preserve the current text size. Bu passing -1 to duration, the default text animation, - * 1000ms, is used. By passing false to animate, the text will be updated without animation. + * By passing -1 to weight, the view preserve the current weight. + * By passing -1 to textSize, the view preserve the current text size. + * Bu passing -1 to duration, the default text animation, 1000ms, is used. + * By passing false to animate, the text will be updated without animation. * * @param fvar an optional text fontVariationSettings. * @param textSize an optional font size. - * @param colors an optional colors array that must be the same size as numLines passed to the - * TextInterpolator + * @param colors an optional colors array that must be the same size as numLines passed to + * the TextInterpolator * @param strokeWidth an optional paint stroke width * @param animate an optional boolean indicating true for showing style transition as animation, - * false for immediate style transition. True by default. + * false for immediate style transition. True by default. * @param duration an optional animation duration in milliseconds. This is ignored if animate is - * false. + * false. * @param interpolator an optional time interpolator. If null is passed, last set interpolator - * will be used. This is ignored if animate is false. + * will be used. This is ignored if animate is false. */ fun setTextStyle( fvar: String? = "", @@ -325,8 +324,7 @@ class TextAnimator( } /** - * Set text style with animation. Similar as fun setTextStyle( fvar: String? = "", textSize: - * ``` + * Set text style with animation. Similar as * fun setTextStyle( * fvar: String? = "", * textSize: Float = -1f, @@ -338,7 +336,6 @@ class TextAnimator( * delay: Long = 0, * onAnimationEnd: Runnable? = null * ) - * ``` * * @param weight an optional style value for `wght` in fontVariationSettings. * @param width an optional style value for `wdth` in fontVariationSettings. @@ -359,13 +356,12 @@ class TextAnimator( delay: Long = 0, onAnimationEnd: Runnable? = null ) { - val fvar = - fontVariationUtils.updateFontVariation( - weight = weight, - width = width, - opticalSize = opticalSize, - roundness = roundness, - ) + val fvar = fontVariationUtils.updateFontVariation( + weight = weight, + width = width, + opticalSize = opticalSize, + roundness = roundness, + ) setTextStyle( fvar = fvar, textSize = textSize, diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt index 02caeeddd774..02caeeddd774 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt index 1290f0097536..1290f0097536 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index a9c53d190cea..00d905652987 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -196,9 +196,9 @@ class ViewHierarchyAnimator { * * @param includeFadeIn true if the animator should also fade in the view and child views. * @param fadeInInterpolator the interpolator to use when fading in the view. Unused if - * [includeFadeIn] is false. - * @param onAnimationEnd an optional runnable that will be run once the animation finishes - * successfully. Will not be run if the animation is cancelled. + * [includeFadeIn] is false. + * @param onAnimationEnd an optional runnable that will be run once the animation + * finishes successfully. Will not be run if the animation is cancelled. */ @JvmOverloads fun animateAddition( @@ -241,10 +241,7 @@ class ViewHierarchyAnimator { // First, fade in the container view val containerDuration = duration / 6 createAndStartFadeInAnimator( - rootView, - containerDuration, - startDelay = 0, - interpolator = fadeInInterpolator + rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator ) // Then, fade in the child views @@ -400,7 +397,7 @@ class ViewHierarchyAnimator { * included by specifying [includeMargins]. * * @param onAnimationEnd an optional runnable that will be run once the animation finishes - * successfully. Will not be run if the animation is cancelled. + * successfully. Will not be run if the animation is cancelled. */ @JvmOverloads fun animateRemoval( @@ -619,7 +616,6 @@ class ViewHierarchyAnimator { * not newly introduced margins are included. * * Base case - * * ``` * 1) origin=TOP * x---------x x---------x x---------x x---------x x---------x @@ -640,11 +636,9 @@ class ViewHierarchyAnimator { * x-----x x-------x | | * x---------x * ``` - * * In case the start and end values differ in the direction of the origin, and * [ignorePreviousValues] is false, the previous values are used and a translation is * included in addition to the view expansion. - * * ``` * origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70) * x @@ -783,7 +777,8 @@ class ViewHierarchyAnimator { includeMargins: Boolean = false, ): Map<Bound, Int> { val marginAdjustment = - if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { + if (includeMargins && + (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams DimenHolder( left = marginLp.leftMargin, @@ -791,9 +786,9 @@ class ViewHierarchyAnimator { right = marginLp.rightMargin, bottom = marginLp.bottomMargin ) - } else { - DimenHolder(0, 0, 0, 0) - } + } else { + DimenHolder(0, 0, 0, 0) + } // These are the end values to use *if* this bound is part of the destination. val endLeft = left - marginAdjustment.left @@ -810,69 +805,60 @@ class ViewHierarchyAnimator { // - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight. return when (destination) { - Hotspot.TOP -> - mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.TOP_RIGHT -> - mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.RIGHT -> - mapOf( - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.BOTTOM_RIGHT -> - mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.BOTTOM -> - mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.BOTTOM_LEFT -> - mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.LEFT -> - mapOf( - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.TOP_LEFT -> - mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.CENTER -> - mapOf( - Bound.LEFT to (endLeft + endRight) / 2, - Bound.RIGHT to (endLeft + endRight) / 2, - Bound.TOP to (endTop + endBottom) / 2, - Bound.BOTTOM to (endTop + endBottom) / 2, - ) + Hotspot.TOP -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.TOP_RIGHT -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.RIGHT -> mapOf( + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.BOTTOM_RIGHT -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.BOTTOM -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.BOTTOM_LEFT -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.LEFT -> mapOf( + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.TOP_LEFT -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.CENTER -> mapOf( + Bound.LEFT to (endLeft + endRight) / 2, + Bound.RIGHT to (endLeft + endRight) / 2, + Bound.TOP to (endTop + endBottom) / 2, + Bound.BOTTOM to (endTop + endBottom) / 2, + ) } } @@ -1097,13 +1083,11 @@ class ViewHierarchyAnimator { animator.startDelay = startDelay animator.duration = duration animator.interpolator = interpolator - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - view.setTag(R.id.tag_alpha_animator, null /* tag */) - } + animator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + view.setTag(R.id.tag_alpha_animator, null /* tag */) } - ) + }) (view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel() view.setTag(R.id.tag_alpha_animator, animator) diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt index e4f6db57f689..e4f6db57f689 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt index dd32851a97cf..dd32851a97cf 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt index c6b70736bc67..c6b70736bc67 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt index 49d1fb423d2b..49d1fb423d2b 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt index 8740d1467296..8740d1467296 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt index 7538f188fb81..7538f188fb81 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt index e42b589f05cf..e42b589f05cf 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt index bce262291f87..bce262291f87 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt index 147669528c5e..147669528c5e 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt index d8e17c9c8204..d8e17c9c8204 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt index 6c175ddf1ea4..6c175ddf1ea4 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt index d4372507e2c4..d4372507e2c4 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt index 91c0a5b635c9..91c0a5b635c9 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt index 7e56f4b3d2b2..7e56f4b3d2b2 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt index b89912709576..b89912709576 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt index c94fad7246fa..c94fad7246fa 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt index df07856e325e..df07856e325e 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt index 78898932249b..78898932249b 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt index 23fcb691ddb4..23fcb691ddb4 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt index 2cd587ffbc45..2cd587ffbc45 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt index b8f4b276dd6a..b8f4b276dd6a 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt index d3c57c91405a..d3c57c91405a 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt index 43d6504fce84..43d6504fce84 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt b/packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt index 35dbb89ad801..35dbb89ad801 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt index 0f63548b6f0c..0f63548b6f0c 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt index 4bc9972dd50f..4bc9972dd50f 100644 --- a/packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index 3fbcf6d77f82..15190214e06e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags @@ -90,8 +91,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { .thenReturn(mock(ImageView::class.java)) `when`(keyguardPasswordView.resources).thenReturn(context.resources) val fakeFeatureFlags = FakeFeatureFlags() - fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) + mSetFlagsRule.enableFlags(AconfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES) keyguardPasswordViewController = KeyguardPasswordViewController( keyguardPasswordView, diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 74c922561343..e2bdc49d590c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake @@ -75,8 +76,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory - @Mock - private lateinit var mSelectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var mKeyguardMessageAreaController: @@ -95,7 +95,6 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { whenever(mKeyguardMessageAreaControllerFactory.create(any())) .thenReturn(mKeyguardMessageAreaController) fakeFeatureFlags = FakeFeatureFlags() - fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false) fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) mKeyguardPatternView = View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView @@ -166,7 +165,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { @Test fun withFeatureFlagOn_oldMessage_isHidden() { - fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) + mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES) mKeyguardPatternViewController.onViewAttached() diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index d41c2497b230..e89316997fb2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -35,7 +35,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -103,8 +102,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { when(mPinBasedInputView.getResources()).thenReturn(getContext().getResources()); FakeFeatureFlags featureFlags = new FakeFeatureFlags(); - featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true); - + mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES); mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener, diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index f1701356c134..1fc2843b1cb8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -36,6 +36,7 @@ import com.android.internal.logging.UiEventLogger import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback import com.android.keyguard.KeyguardSecurityModel.SecurityMode +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate import com.android.systemui.biometrics.SideFpsController @@ -196,12 +197,12 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true) featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false) featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) + mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES) keyguardPasswordViewController = KeyguardPasswordViewController( keyguardPasswordView, diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 84d735430edd..cbcca55f6a34 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -24,10 +24,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any @@ -81,8 +81,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null) as KeyguardSimPinView val fakeFeatureFlags = FakeFeatureFlags() - fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) + mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) underTest = KeyguardSimPinViewController( simPinView, diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 7b1f302da6e8..45a60199984b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -24,10 +24,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any @@ -75,8 +75,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null) as KeyguardSimPukView val fakeFeatureFlags = FakeFeatureFlags() - fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) - + mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) underTest = KeyguardSimPukViewController( simPukView, diff --git a/packages/SystemUI/shared/res/values/ids.xml b/packages/SystemUI/shared/res/values/ids.xml new file mode 100644 index 000000000000..1ff2f0eff215 --- /dev/null +++ b/packages/SystemUI/shared/res/values/ids.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <!-- ID of the smartspace card view. --> + <item type="id" name="bc_smartspace_view" /> + <!-- ID of the smartspace date view. --> + <item type="id" name="date_smartspace_view" /> + <!-- ID of the smartspace weather view. --> + <item type="id" name="weather_smartspace_view" /> +</resources> diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt index 1ee58deb501c..5e76801aaca3 100644 --- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -41,6 +41,7 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.settings.SecureSettings import java.io.PrintWriter import javax.inject.Inject +import dagger.Lazy /** * Handles active unlock settings changes. @@ -51,6 +52,7 @@ class ActiveUnlockConfig @Inject constructor( private val secureSettings: SecureSettings, private val contentResolver: ContentResolver, private val selectedUserInteractor: SelectedUserInteractor, + private val keyguardUpdateMonitor: Lazy<KeyguardUpdateMonitor>, dumpManager: DumpManager ) : Dumpable { @@ -96,7 +98,6 @@ class ActiveUnlockConfig @Inject constructor( UNDER_DISPLAY_FINGERPRINT(3), } - var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null private var requestActiveUnlockOnWakeup = false private var requestActiveUnlockOnUnlockIntent = false private var requestActiveUnlockOnBioFail = false @@ -316,7 +317,7 @@ class ActiveUnlockConfig @Inject constructor( return false } - keyguardUpdateMonitor?.let { + keyguardUpdateMonitor.get().let { val anyFaceEnrolled = it.isFaceEnabledAndEnrolled val anyFingerprintEnrolled = it.isUnlockWithFingerprintPossible( selectedUserInteractor.getSelectedUserId()) @@ -369,13 +370,13 @@ class ActiveUnlockConfig @Inject constructor( }") pw.println("Current state:") - keyguardUpdateMonitor?.let { + keyguardUpdateMonitor.get().let { pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" + "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}") pw.println(" isFaceEnabledAndEnrolled=${it.isFaceEnabledAndEnrolled}") pw.println(" fpUnlockPossible=${ it.isUnlockWithFingerprintPossible(selectedUserInteractor.getSelectedUserId())}") pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}") - } ?: pw.println(" keyguardUpdateMonitor is uninitialized") + } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 714fe64f1a8f..66f965aea76f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -30,13 +30,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.Flags; import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.bouncer.ui.BouncerMessageView; import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.log.BouncerLogger; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; @@ -108,7 +108,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private void updateMessageAreaVisibility() { if (mMessageAreaController == null) return; - if (mFeatureFlags.isEnabled(Flags.REVAMPED_BOUNCER_MESSAGES)) { + if (Flags.revampedBouncerMessages()) { mMessageAreaController.disable(); } else { mMessageAreaController.setIsVisible(true); @@ -182,15 +182,13 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> public void bindMessageView( @NonNull BouncerMessageInteractor bouncerMessageInteractor, KeyguardMessageAreaController.Factory messageAreaControllerFactory, - BouncerLogger bouncerLogger, - FeatureFlags featureFlags) { + BouncerLogger bouncerLogger) { BouncerMessageView bouncerMessageView = (BouncerMessageView) mView.getBouncerMessageView(); if (bouncerMessageView != null) { BouncerMessageViewBinder.bind(bouncerMessageView, bouncerMessageInteractor, messageAreaControllerFactory, - bouncerLogger, - featureFlags); + bouncerLogger); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index cce2018f733f..5e35e7764dd8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -31,7 +31,6 @@ import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPin; import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPuk; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; -import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; @@ -1164,7 +1163,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mLockPatternUtils.reportFailedPasswordAttempt(userId); if (timeoutMs > 0) { mLockPatternUtils.reportPasswordLockout(timeoutMs, userId); - if (!mFeatureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) { + if (!com.android.systemui.Flags.revampedBouncerMessages()) { mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils, mSecurityModel.getSecurityMode(userId)); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 9c61a8a3cd8b..f3cd01f908da 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -111,6 +111,7 @@ import com.android.keyguard.logging.KeyguardUpdateMonitorLogger; import com.android.settingslib.Utils; import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; @@ -175,7 +176,7 @@ import javax.inject.Provider; * to be updated. */ @SysUISingleton -public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable { +public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable, CoreStartable { private static final String TAG = "KeyguardUpdateMonitor"; private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600; @@ -347,6 +348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final LatencyTracker mLatencyTracker; private final StatusBarStateController mStatusBarStateController; private final Executor mBackgroundExecutor; + private final Executor mMainExecutor; private final SensorPrivacyManager mSensorPrivacyManager; private final ActiveUnlockConfig mActiveUnlockConfig; private final IDreamManager mDreamManager; @@ -354,7 +356,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Nullable private final FingerprintManager mFpm; @Nullable + private final BiometricManager mBiometricManager; + @Nullable private KeyguardFaceAuthInteractor mFaceAuthInteractor; + private final DevicePostureController mDevicePostureController; private final TaskStackChangeListeners mTaskStackChangeListeners; private final IActivityTaskManager mActivityTaskManager; private final SelectedUserInteractor mSelectedUserInteractor; @@ -370,7 +375,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mIsDreaming; private boolean mLogoutEnabled; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; + private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; /** * Short delay before restarting fingerprint authentication after a successful try. This should @@ -2105,6 +2110,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); mStrongAuthTracker = new StrongAuthTracker(context); mBackgroundExecutor = backgroundExecutor; + mMainExecutor = mainExecutor; mBroadcastDispatcher = broadcastDispatcher; mInteractionJankMonitor = interactionJankMonitor; mLatencyTracker = latencyTracker; @@ -2126,7 +2132,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mDevicePolicyManager = devicePolicyManager; mPackageManager = packageManager; mFpm = fingerprintManager; - mActiveUnlockConfig.setKeyguardUpdateMonitor(this); + mBiometricManager = biometricManager; mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger( R.integer.config_face_auth_supported_posture); mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig; @@ -2134,10 +2140,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getResources().getStringArray( R.array.config_fingerprint_listen_on_occluding_activity_packages)) .collect(Collectors.toSet()); + mDevicePostureController = devicePostureController; mTaskStackChangeListeners = taskStackChangeListeners; mActivityTaskManager = activityTaskManagerService; mSelectedUserInteractor = selectedUserInteractor; + mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null); + mIsSystemUser = mUserManager.isSystemUser(); + mHandler = new Handler(mainLooper) { @Override public void handleMessage(Message msg) { @@ -2252,6 +2262,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }; + mTimeFormatChangeObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + mHandler.sendMessage(mHandler.obtainMessage( + MSG_TIME_FORMAT_UPDATE, + Settings.System.getString( + mContext.getContentResolver(), + Settings.System.TIME_12_24))); + } + }; + } + + @Override + public void start() { // Since device can't be un-provisioned, we only need to register a content observer // to update mDeviceProvisioned when we are... if (!mDeviceProvisioned) { @@ -2297,7 +2321,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler, UserHandle.ALL); mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener); - mUserTracker.addCallback(mUserChangedCallback, mainExecutor); + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); mTrustManager.registerTrustListener(this); @@ -2318,8 +2342,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback); } - if (biometricManager != null) { - biometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback); + if (mBiometricManager != null) { + mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback); } // in case authenticators aren't registered yet at this point: @@ -2327,7 +2351,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAllAuthenticatorsRegistered( @BiometricAuthenticator.Modality int modality) { - mainExecutor.execute( + mMainExecutor.execute( () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE)); } @@ -2335,7 +2359,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { mHandler.obtainMessage(MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED, modality, 0) .sendToTarget(); - mainExecutor.execute( + mMainExecutor.execute( () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE)); } @@ -2353,12 +2377,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }); if (mConfigFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) { - devicePostureController.addCallback(mPostureCallback); + mDevicePostureController.addCallback(mPostureCallback); } updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); - mIsSystemUser = mUserManager.isSystemUser(); int user = mSelectedUserInteractor.getSelectedUserId(true); mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user)); mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled(); @@ -2377,22 +2400,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); initializeSimState(); - mTimeFormatChangeObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - mHandler.sendMessage(mHandler.obtainMessage( - MSG_TIME_FORMAT_UPDATE, - Settings.System.getString( - mContext.getContentResolver(), - Settings.System.TIME_12_24))); - } - }; - mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.TIME_12_24), false, mTimeFormatChangeObserver, UserHandle.USER_ALL); - - mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null); } private void initializeSimState() { diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java index 7295936ddc7c..4c9782cdf36c 100644 --- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java +++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java @@ -33,6 +33,9 @@ import java.io.PrintWriter; * abstract fun bind(impl: FoobarStartable): CoreStartable * </pre> * + * If your CoreStartable depends on different CoreStartables starting before it, use a + * {@link com.android.systemui.startable.Dependencies} annotation to list out those dependencies. + * * @see SystemUIApplication#startServicesIfNeeded() */ public interface CoreStartable extends Dumpable { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 01f6971de373..8aae20651a10 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.annotation.SuppressLint; import android.app.ActivityThread; import android.app.Application; import android.app.Notification; @@ -26,27 +27,33 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.os.Bundle; -import android.os.Looper; import android.os.Process; -import android.os.SystemProperties; import android.os.Trace; -import android.os.UserHandle; import android.util.Log; import android.util.TimingsTraceLog; import android.view.SurfaceControl; import android.view.ThreadedRenderer; import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + import com.android.internal.protolog.common.ProtoLog; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dump.DumpManager; import com.android.systemui.res.R; +import com.android.systemui.startable.Dependencies; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.NotificationChannels; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Comparator; +import java.util.HashSet; import java.util.Map; +import java.util.StringJoiner; import java.util.TreeMap; import javax.inject.Provider; @@ -78,10 +85,17 @@ public class SystemUIApplication extends Application implements ProtoLog.REQUIRE_PROTOLOGTOOL = false; } + @VisibleForTesting + @Override + public void attachBaseContext(Context base) { + super.attachBaseContext(base); + } + protected GlobalRootComponent getRootComponent() { return mInitializer.getRootComponent(); } + @SuppressLint("RegisterReceiverViaContext") @Override public void onCreate() { super.onCreate(); @@ -96,9 +110,11 @@ public class SystemUIApplication extends Application implements mBootCompleteCache = mSysUIComponent.provideBootCacheImpl(); log.traceEnd(); + GlobalRootComponent rootComponent = mInitializer.getRootComponent(); + // Enable Looper trace points. // This allows us to see Handler callbacks on traces. - Looper.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP); + rootComponent.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP); // Set the application theme that is inherited by all services. Note that setting the // application theme in the manifest does only work for activities. Keep this in sync with @@ -106,15 +122,17 @@ public class SystemUIApplication extends Application implements setTheme(R.style.Theme_SystemUI); View.setTraceLayoutSteps( - SystemProperties.getBoolean("persist.debug.trace_layouts", false)); + rootComponent.getSystemPropertiesHelper() + .getBoolean("persist.debug.trace_layouts", false)); View.setTracedRequestLayoutClassClass( - SystemProperties.get("persist.debug.trace_request_layout_class", null)); + rootComponent.getSystemPropertiesHelper() + .get("persist.debug.trace_request_layout_class", null)); if (Flags.enableLayoutTracing()) { View.setTraceLayoutSteps(true); } - if (Process.myUserHandle().equals(UserHandle.SYSTEM)) { + if (rootComponent.getProcessWrapper().isSystemUser()) { IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED); bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); @@ -175,8 +193,8 @@ public class SystemUIApplication extends Application implements } /** - * Makes sure that all the SystemUI services are running. If they are already running, this is a - * no-op. This is needed to conditinally start all the services, as we only need to have it in + * Makes sure that all the CoreStartables are running. If they are already running, this is a + * no-op. This is needed to conditionally start all the services, as we only need to have it in * the main process. * <p>This method must only be called from the main thread.</p> */ @@ -221,7 +239,8 @@ public class SystemUIApplication extends Application implements if (!mBootCompleteCache.isBootComplete()) { // check to see if maybe it was already completed long before we began // see ActivityManagerService.finishBooting() - if ("1".equals(SystemProperties.get("sys.boot_completed"))) { + if ("1".equals(getRootComponent().getSystemPropertiesHelper() + .get("sys.boot_completed"))) { mBootCompleteCache.setBootComplete(); if (DEBUG) { Log.v(TAG, "BOOT_COMPLETED was already sent"); @@ -237,17 +256,78 @@ public class SystemUIApplication extends Application implements Trace.TRACE_TAG_APP); log.traceBegin(metricsPrefix); - int i = 0; - for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) { - String clsName = entry.getKey().getName(); - int j = i; // Copied to make lambda happy. - timeInitialization( - clsName, - () -> mServices[j] = startStartable(clsName, entry.getValue()), - log, - metricsPrefix); - i++; + HashSet<Class<?>> startedStartables = new HashSet<>(); + + // Perform a form of topological sort: + // 1) Iterate through a queue of all non-started startables + // If the startable has all of its dependencies met + // - start it + // Else + // - enqueue it for the next iteration + // 2) If anything was started and the "next" queue is not empty, loop back to 1 + // 3) If we're done looping and there are any non-started startables left, throw an error. + // + // This "sort" is not very optimized. We assume that most CoreStartables don't have many + // dependencies - zero in fact. We assume two or three iterations of this loop will be + // enough. If that ever changes, it may be worth revisiting. + + log.traceBegin("Topologically start Core Startables"); + boolean startedAny = false; + ArrayDeque<Map.Entry<Class<?>, Provider<CoreStartable>>> queue; + ArrayDeque<Map.Entry<Class<?>, Provider<CoreStartable>>> nextQueue = + new ArrayDeque<>(startables.entrySet()); + int numIterations = 0; + + int serviceIndex = 0; + + do { + queue = nextQueue; + nextQueue = new ArrayDeque<>(startables.size()); + + while (!queue.isEmpty()) { + Map.Entry<Class<?>, Provider<CoreStartable>> entry = queue.removeFirst(); + + Class<?> cls = entry.getKey(); + Dependencies dep = cls.getAnnotation(Dependencies.class); + Class<? extends CoreStartable>[] deps = (dep == null ? null : dep.value()); + if (deps == null || startedStartables.containsAll(Arrays.asList(deps))) { + String clsName = cls.getName(); + int i = serviceIndex; // Copied to make lambda happy. + timeInitialization( + clsName, + () -> mServices[i] = startStartable(clsName, entry.getValue()), + log, + metricsPrefix); + startedStartables.add(cls); + startedAny = true; + serviceIndex++; + } else { + nextQueue.add(entry); + } + } + numIterations++; + } while (startedAny && !nextQueue.isEmpty()); // if none were started, stop. + + if (!nextQueue.isEmpty()) { // If some startables were left over, throw an error. + while (!nextQueue.isEmpty()) { + Map.Entry<Class<?>, Provider<CoreStartable>> entry = nextQueue.removeFirst(); + Class<?> cls = entry.getKey(); + Dependencies dep = cls.getAnnotation(Dependencies.class); + Class<? extends CoreStartable>[] deps = (dep == null ? null : dep.value()); + StringJoiner stringJoiner = new StringJoiner(", "); + for (int i = 0; deps != null && i < deps.length; i++) { + if (!startedStartables.contains(deps[i])) { + stringJoiner.add(deps[i].getName()); + } + } + Log.e(TAG, "Failed to start " + cls.getName() + + ". Missing dependencies: [" + stringJoiner + "]"); + } + + throw new RuntimeException("Failed to start all CoreStartables. Check logcat!"); } + Log.i(TAG, "Topological CoreStartables completed in " + numIterations + " iterations"); + log.traceEnd(); if (vendorComponent != null) { timeInitialization( @@ -258,8 +338,8 @@ public class SystemUIApplication extends Application implements metricsPrefix); } - for (i = 0; i < mServices.length; i++) { - final CoreStartable service = mServices[i]; + for (serviceIndex = 0; serviceIndex < mServices.length; serviceIndex++) { + final CoreStartable service = mServices[serviceIndex]; if (mBootCompleteCache.isBootComplete()) { notifyBootCompleted(service); } @@ -308,10 +388,14 @@ public class SystemUIApplication extends Application implements Trace.TRACE_TAG_APP, clsName + ".newInstance()"); } try { - startable = (CoreStartable) Class.forName(clsName).newInstance(); + startable = (CoreStartable) Class.forName(clsName) + .getDeclaredConstructor() + .newInstance(); } catch (ClassNotFoundException - | IllegalAccessException - | InstantiationException ex) { + | IllegalAccessException + | InstantiationException + | NoSuchMethodException + | InvocationTargetException ex) { throw new RuntimeException(ex); } finally { Trace.endSection(); @@ -344,7 +428,7 @@ public class SystemUIApplication extends Application implements } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(@NonNull Configuration newConfig) { if (mServicesStarted) { ConfigurationController configController = mSysUIComponent.getConfigurationController(); if (Trace.isEnabled()) { @@ -363,7 +447,7 @@ public class SystemUIApplication extends Application implements @Override public void setContextAvailableCallback( - SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) { + @NonNull SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) { mContextAvailableCallback = callback; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 83d415fa936e..ab23564a1df4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -403,13 +403,6 @@ public class AuthContainerView extends LinearLayout final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( R.layout.biometric_prompt_layout, null, false); - /** - * View is only set visible in BiometricViewSizeBinder once PromptSize is determined - * that accounts for iconView size, to prevent prompt resizing being visible to the - * user. - * TODO(b/288175072): May be able to remove this once constraint layout is implemented - */ - view.setVisibility(View.INVISIBLE); mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController, // TODO(b/201510778): This uses the wrong timeout in some cases getJankListener(view, TRANSIT, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index a7fb6f72a41e..90e4a3821634 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -97,13 +97,7 @@ object BiometricViewBinder { val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay) val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon) - /** - * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that - * accounts for iconView size, to prevent prompt resizing being visible to the user. - * - * TODO(b/288175072): May be able to remove this once constraint layout is implemented - */ - iconView.addLottieOnCompositionLoadedListener { viewModel.setIsIconViewLoaded(true) } + PromptIconViewBinder.bind( iconView, iconOverlayView, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index f340bd81f951..7e16d1e1d668 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -30,6 +30,7 @@ import androidx.core.animation.addListener import androidx.core.view.doOnLayout import androidx.core.view.isGone import androidx.lifecycle.lifecycleScope +import com.android.systemui.res.R import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.ui.BiometricPromptLayout @@ -40,8 +41,6 @@ import com.android.systemui.biometrics.ui.viewmodel.isMedium import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall import com.android.systemui.biometrics.ui.viewmodel.isSmall import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.res.R -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch /** Helper for [BiometricViewBinder] to handle resize transitions. */ @@ -93,22 +92,8 @@ object BiometricViewSizeBinder { // TODO(b/251476085): migrate the legacy panel controller and simplify this view.repeatWhenAttached { var currentSize: PromptSize? = null - lifecycleScope.launch { - /** - * View is only set visible in BiometricViewSizeBinder once PromptSize is - * determined that accounts for iconView size, to prevent prompt resizing being - * visible to the user. - * - * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint - * layout is implemented - */ - combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect { - (isIconViewLoaded, size) -> - if (!isIconViewLoaded) { - return@collect - } - + viewModel.size.collect { size -> // prepare for animated size transitions for (v in viewsToHideWhenSmall) { v.showTextOrHide(forceHide = size.isSmall) @@ -211,9 +196,8 @@ object BiometricViewSizeBinder { } } } + currentSize = size - view.visibility = View.VISIBLE - viewModel.setIsIconViewLoaded(false) notifyAccessibilityChanged() } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index d899827ebb2e..6d0a58e202bd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -192,28 +192,6 @@ constructor( val iconViewModel: PromptIconViewModel = PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor) - private val _isIconViewLoaded = MutableStateFlow(false) - - /** - * For prompts with an iconView, false until the prompt's iconView animation has been loaded in - * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon - * asset to be loaded before determining the prompt size. - */ - val isIconViewLoaded: Flow<Boolean> = - combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded - -> - if (credentialKind is PromptKind.Biometric) { - isIconViewLoaded - } else { - true - } - } - - // Sets whether the prompt's iconView animation has been loaded in the view yet. - fun setIsIconViewLoaded(iconViewLoaded: Boolean) { - _isIconViewLoaded.value = iconViewLoaded - } - /** Padding for prompt UI elements */ val promptPadding: Flow<Rect> = combine(size, displayStateInteractor.currentRotation) { size, rotation -> diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt index f612f9afed77..b587ed846a38 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt @@ -22,6 +22,7 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.Flags import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.data.repository.BouncerMessageRepository @@ -29,8 +30,6 @@ import com.android.systemui.bouncer.shared.model.BouncerMessageModel import com.android.systemui.bouncer.shared.model.Message import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository @@ -100,7 +99,6 @@ constructor( private val repository: BouncerMessageRepository, private val userRepository: UserRepository, private val countDownTimerUtil: CountDownTimerUtil, - private val featureFlags: FeatureFlags, private val updateMonitor: KeyguardUpdateMonitor, trustRepository: TrustRepository, biometricSettingsRepository: BiometricSettingsRepository, @@ -229,7 +227,7 @@ constructor( } fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) { - if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return + if (!Flags.revampedBouncerMessages()) return val callback = object : CountDownTimerCallback { @@ -250,7 +248,7 @@ constructor( } fun onPrimaryAuthIncorrectAttempt() { - if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return + if (!Flags.revampedBouncerMessages()) return repository.setMessage( incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) @@ -258,21 +256,21 @@ constructor( } fun setFingerprintAcquisitionMessage(value: String?) { - if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return + if (!Flags.revampedBouncerMessages()) return repository.setMessage( defaultMessage(currentSecurityMode, value, isFingerprintAuthCurrentlyAllowed.value) ) } fun setFaceAcquisitionMessage(value: String?) { - if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return + if (!Flags.revampedBouncerMessages()) return repository.setMessage( defaultMessage(currentSecurityMode, value, isFingerprintAuthCurrentlyAllowed.value) ) } fun setCustomMessage(value: String?) { - if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return + if (!Flags.revampedBouncerMessages()) return repository.setMessage( defaultMessage(currentSecurityMode, value, isFingerprintAuthCurrentlyAllowed.value) @@ -283,7 +281,7 @@ constructor( get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) fun onPrimaryBouncerUserInput() { - if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return + if (!Flags.revampedBouncerMessages()) return repository.setMessage(defaultMessage) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt index 5a59012a74b8..b8e6ad6956fe 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt @@ -23,11 +23,10 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.keyguard.BouncerKeyguardMessageArea import com.android.keyguard.KeyguardMessageArea import com.android.keyguard.KeyguardMessageAreaController +import com.android.systemui.Flags import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.shared.model.Message import com.android.systemui.bouncer.ui.BouncerMessageView -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.BouncerLogger import kotlinx.coroutines.launch @@ -39,10 +38,9 @@ object BouncerMessageViewBinder { interactor: BouncerMessageInteractor, factory: KeyguardMessageAreaController.Factory, bouncerLogger: BouncerLogger, - featureFlags: FeatureFlags ) { view.repeatWhenAttached { - if (!featureFlags.isEnabled(Flags.REVAMPED_BOUNCER_MESSAGES)) { + if (!Flags.revampedBouncerMessages()) { view.primaryMessageView?.disable() view.secondaryMessageView?.disable() return@repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index 5dcd6615509d..cc387e98aa23 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -32,7 +32,6 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.bouncer.ui.BouncerViewDelegate import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel -import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.BouncerLogger @@ -53,7 +52,6 @@ object KeyguardBouncerViewBinder { messageAreaControllerFactory: KeyguardMessageAreaController.Factory, bouncerMessageInteractor: BouncerMessageInteractor, bouncerLogger: BouncerLogger, - featureFlags: FeatureFlags, selectedUserInteractor: SelectedUserInteractor, ) { // Builds the KeyguardSecurityContainerController from bouncer view group. @@ -141,8 +139,7 @@ object KeyguardBouncerViewBinder { it.bindMessageView( bouncerMessageInteractor, messageAreaControllerFactory, - bouncerLogger, - featureFlags + bouncerLogger ) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java index 9e8c0ec7423e..e78ce3bbb0d1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java @@ -17,8 +17,12 @@ package com.android.systemui.dagger; import android.content.Context; +import android.os.Looper; import com.android.systemui.dagger.qualifiers.InstrumentationTest; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.SystemPropertiesHelper; +import com.android.systemui.process.ProcessWrapper; import com.android.systemui.util.InitializationChecker; import dagger.BindsInstance; @@ -58,4 +62,20 @@ public interface GlobalRootComponent { * Returns an {@link InitializationChecker}. */ InitializationChecker getInitializationChecker(); + + /** + * Returns the main looper for this process. + */ + @Main + Looper getMainLooper(); + + /** + * Returns a {@link SystemPropertiesHelper}. + */ + SystemPropertiesHelper getSystemPropertiesHelper(); + + /** + * Returns a {@link ProcessWrapper} + */ + ProcessWrapper getProcessWrapper(); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 1b350055e8de..bb0c2733e511 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -208,10 +208,6 @@ object Flags { val WALLPAPER_PICKER_GRID_APPLY_BUTTON = unreleasedFlag("wallpaper_picker_grid_apply_button") - /** Provide new auth messages on the bouncer. */ - // TODO(b/277961132): Tracking bug. - @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages") - /** Keyguard Migration */ // TODO(b/297037052): Tracking bug. diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt index 6c160975587b..6fa20de1fb7f 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt @@ -17,19 +17,22 @@ package com.android.systemui.flags import android.os.SystemProperties -import com.android.systemui.dagger.SysUISingleton - import javax.inject.Inject +import javax.inject.Singleton /** * Proxy to make {@link SystemProperties} easily testable. */ -@SysUISingleton +@Singleton open class SystemPropertiesHelper @Inject constructor() { fun get(name: String): String { return SystemProperties.get(name) } + fun get(name: String, def: String?): String { + return SystemProperties.get(name, def) + } + fun getBoolean(name: String, default: Boolean): Boolean { return SystemProperties.getBoolean(name, default) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 3925dd1620b4..13e38358477a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -34,6 +34,7 @@ import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.keyguard.mediator.ScreenOnCoordinator; +import com.android.systemui.CoreStartable; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; @@ -79,9 +80,12 @@ import com.android.systemui.util.time.SystemClock; import com.android.systemui.wallpapers.data.repository.WallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; +import dagger.Binds; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; import java.util.concurrent.Executor; @@ -105,7 +109,7 @@ import kotlinx.coroutines.CoroutineDispatcher; StartKeyguardTransitionModule.class, ResourceTrimmerModule.class, }) -public class KeyguardModule { +public interface KeyguardModule { /** * Provides our instance of KeyguardViewMediator which is considered optional. */ @@ -207,13 +211,19 @@ public class KeyguardModule { /** */ @Provides - public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) { + static ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) { return viewMediator.getViewMediatorCallback(); } /** */ @Provides - public KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() { + static KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() { return new KeyguardQuickAffordancesMetricsLoggerImpl(); } + + /** Binds {@link KeyguardUpdateMonitor} as a {@link CoreStartable}. */ + @Binds + @IntoMap + @ClassKey(KeyguardUpdateMonitor.class) + CoreStartable bindsKeyguardUpdateMonitor(KeyguardUpdateMonitor keyguardUpdateMonitor); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index e36819b54524..92270ad31664 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R +import com.android.systemui.shared.R as sharedR import kotlinx.coroutines.launch object KeyguardSmartspaceViewBinder { @@ -69,9 +70,10 @@ object KeyguardSmartspaceViewBinder { smartspaceViewModel.isSmartspaceEnabled && smartspaceViewModel.isDateWeatherDecoupled ) { - val dateView = constraintLayout.requireViewById<View>(smartspaceViewModel.dateId) + val dateView = + constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view) val weatherView = - constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId) + constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view) addView(weatherView) addView(dateView) } @@ -88,8 +90,10 @@ object KeyguardSmartspaceViewBinder { smartspaceViewModel.isSmartspaceEnabled && smartspaceViewModel.isDateWeatherDecoupled ) { - val dateView = smartspaceViewModel.dateView - val weatherView = smartspaceViewModel.weatherView + val dateView = + constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view) + val weatherView = + constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view) removeView(weatherView) removeView(dateView) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index d89e1e41d1bc..1ccc6ccf2cec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R +import com.android.systemui.shared.R as sharedR import javax.inject.Inject /** Adds a layer to group elements for translation for burn-in preventation */ @@ -87,7 +88,7 @@ constructor( burnInLayer.apply { if (smartspaceViewModel.isSmartspaceEnabled) { val smartspaceView = - constraintLayout.requireViewById<View>(smartspaceViewModel.smartspaceViewId) + constraintLayout.requireViewById<View>(sharedR.id.bc_smartspace_view) addView(smartspaceView) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index b429ab4fac0a..f560b5f068c9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -32,6 +32,7 @@ import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R +import com.android.systemui.shared.R as sharedR import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker @@ -117,7 +118,7 @@ constructor( } constraintSet.apply { if (migrateClocksToBlueprint()) { - connect(nicId, TOP, smartspaceViewModel.smartspaceViewId, BOTTOM, bottomMargin) + connect(nicId, TOP, sharedR.id.bc_smartspace_view, BOTTOM, bottomMargin) setGoneMargin(nicId, BOTTOM, bottomMargin) } else { connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index 656c75c7bfec..b5f32c8a5608 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -32,7 +32,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFaceLayout import com.android.systemui.res.R @@ -55,7 +54,6 @@ open class ClockSection constructor( private val clockInteractor: KeyguardClockInteractor, protected val keyguardClockViewModel: KeyguardClockViewModel, - private val smartspaceViewModel: KeyguardSmartspaceViewModel, private val context: Context, private val splitShadeStateController: SplitShadeStateController, ) : KeyguardSection() { @@ -119,8 +117,8 @@ constructor( com.android.systemui.customization.R.dimen.small_clock_padding_top ) + context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) - largeClockTopMargin += smartspaceViewModel.getDimen(DATE_WEATHER_VIEW_HEIGHT) - largeClockTopMargin += smartspaceViewModel.getDimen(ENHANCED_SMARTSPACE_HEIGHT) + largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT) + largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT) if (!keyguardClockViewModel.useLargeClock) { largeClockTopMargin -= context.resources.getDimensionPixelSize( @@ -163,6 +161,12 @@ constructor( } } + private fun getDimen(name: String): Int { + val res = context.packageManager.getResourcesForApplication(context.packageName) + val id = res.getIdentifier(name, "dimen", context.packageName) + return res.getDimensionPixelSize(id) + } + companion object { private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height" private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index e7b6e44450bd..b0eee0a68b1f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.shared.R as sharedR import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator @@ -80,7 +81,7 @@ constructor( connect( R.id.nssl_placeholder, TOP, - smartspaceViewModel.smartspaceViewId, + sharedR.id.bc_smartspace_view, BOTTOM, bottomMargin ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 8cd51cd3b1e3..eacd466bc473 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context +import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM @@ -44,13 +45,9 @@ constructor( val smartspaceController: LockscreenSmartspaceController, val keyguardUnlockAnimationController: KeyguardUnlockAnimationController, ) : KeyguardSection() { - var smartspaceView by keyguardSmartspaceViewModel::smartspaceView - var weatherView by keyguardSmartspaceViewModel::weatherView - var dateView by keyguardSmartspaceViewModel::dateView - - val smartspaceViewId = keyguardSmartspaceViewModel.smartspaceViewId - val weatherViewId = keyguardSmartspaceViewModel.weatherId - val dateViewId = keyguardSmartspaceViewModel.dateId + private var smartspaceView: View? = null + private var weatherView: View? = null + private var dateView: View? = null override fun addViews(constraintLayout: ConstraintLayout) { if (!migrateClocksToBlueprint()) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt index 1302bfa6fc31..19ba1aa4763a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt @@ -20,7 +20,6 @@ import android.content.Context import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject @@ -30,17 +29,9 @@ class SplitShadeClockSection constructor( clockInteractor: KeyguardClockInteractor, keyguardClockViewModel: KeyguardClockViewModel, - smartspaceViewModel: KeyguardSmartspaceViewModel, context: Context, splitShadeStateController: SplitShadeStateController, -) : - ClockSection( - clockInteractor, - keyguardClockViewModel, - smartspaceViewModel, - context, - splitShadeStateController - ) { +) : ClockSection(clockInteractor, keyguardClockViewModel, context, splitShadeStateController) { override fun applyDefaultConstraints(constraints: ConstraintSet) { super.applyDefaultConstraints(constraints) val largeClockEndGuideline = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt index 5afdbaa47d95..f20ab06bcda9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.media.controls.ui.KeyguardMediaController import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.shared.R as sharedR import javax.inject.Inject /** Aligns media on left side for split shade, below smartspace, date, and weather. */ @@ -90,9 +91,9 @@ constructor( Barrier.BOTTOM, 0, *intArrayOf( - keyguardSmartspaceViewModel.smartspaceViewId, - keyguardSmartspaceViewModel.dateId, - keyguardSmartspaceViewModel.weatherId, + sharedR.id.bc_smartspace_view, + sharedR.id.date_smartspace_view, + sharedR.id.weather_smartspace_view, ) ) connect(mediaContainerId, TOP, smartSpaceBarrier, BOTTOM) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt index 26e7ee8f561b..a1dd720a82f1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt @@ -16,47 +16,66 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.content.Context -import android.util.Log -import android.view.View import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn @SysUISingleton class KeyguardSmartspaceViewModel @Inject -constructor(val context: Context, val smartspaceController: LockscreenSmartspaceController) { +constructor( + @Application applicationScope: CoroutineScope, + smartspaceController: LockscreenSmartspaceController, + keyguardClockViewModel: KeyguardClockViewModel, +) { + /** Whether the smartspace section is available in the build. */ val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled() + /** Whether the weather area is available in the build. */ + // TODO(b/317891876): this should be a Flow as the value can change over time. val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled() + /** Whether the data and weather areas are decoupled in the build. */ val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled() - val smartspaceViewId: Int - get() = getId("bc_smartspace_view") - val dateId: Int - get() = getId("date_smartspace_view") + /** Whether the date area should be visible. */ + val isDateVisible: StateFlow<Boolean> = + keyguardClockViewModel.hasCustomWeatherDataDisplay + .map { !it } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = !keyguardClockViewModel.hasCustomWeatherDataDisplay.value, + ) - val weatherId: Int - get() = getId("weather_smartspace_view") - - var smartspaceView: View? = null - var weatherView: View? = null - var dateView: View? = null - - private fun getId(name: String): Int { - return context.resources.getIdentifier(name, "id", context.packageName).also { - if (it == 0) { - Log.d(TAG, "Cannot resolve id $name") + /** Whether the weather area should be visible. */ + val isWeatherVisible: StateFlow<Boolean> = + keyguardClockViewModel.hasCustomWeatherDataDisplay + .map { clockIncludesCustomWeatherDisplay -> + isWeatherVisible( + clockIncludesCustomWeatherDisplay = clockIncludesCustomWeatherDisplay, + isWeatherEnabled = isWeatherEnabled, + ) } - } - } - fun getDimen(name: String): Int { - val res = context.packageManager.getResourcesForApplication(context.packageName) - val id = res.getIdentifier(name, "dimen", context.packageName) - return res.getDimensionPixelSize(id) - } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = + isWeatherVisible( + clockIncludesCustomWeatherDisplay = + keyguardClockViewModel.hasCustomWeatherDataDisplay.value, + isWeatherEnabled = isWeatherEnabled, + ) + ) - companion object { - private val TAG = KeyguardSmartspaceViewModel::class.java.simpleName + private fun isWeatherVisible( + clockIncludesCustomWeatherDisplay: Boolean, + isWeatherEnabled: Boolean, + ): Boolean { + return !clockIncludesCustomWeatherDisplay && isWeatherEnabled } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 3cf468f302b2..8162d587f4f7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -250,7 +250,6 @@ public class NotificationShadeWindowViewController implements Dumpable { messageAreaControllerFactory, bouncerMessageInteractor, bouncerLogger, - featureFlagsClassic, selectedUserInteractor); if (DeviceEntryUdfpsRefactor.isEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt b/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt new file mode 100644 index 000000000000..5e57f1d1a11f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.startable + +import com.android.systemui.CoreStartable +import kotlin.reflect.KClass + +/** + * Allows a [CoreStartable] to declare that it must be started after its dependencies. + * + * This creates a partial, topological ordering. See [com.android.systemui.SystemUIApplication] for + * how this ordering is enforced at runtime. + */ +@MustBeDocumented +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class Dependencies(vararg val value: KClass<out CoreStartable> = []) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 633510d9c438..c9df317508f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar; +import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; +import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; @@ -22,6 +24,7 @@ import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_NULL; import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; +import static android.app.Flags.keyguardPrivateNotifications; import static android.os.Flags.allowPrivateProfile; import static com.android.systemui.DejankUtils.whitelistIpcs; @@ -47,7 +50,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.Log; -import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -149,6 +151,25 @@ public class NotificationLockscreenUserManagerImpl implements new ListenerSet<>(); private final Collection<Uri> mLockScreenUris = new ArrayList<>(); + protected final BroadcastReceiver mKeyguardReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + + if (ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED.equals(action)) { + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + mKeyguardAllowingNotifications = + intent.getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false); + if (mCurrentUserId == getSendingUserId()) { + boolean changed = updateLockscreenNotificationSetting(); + if (changed) { + notifyNotificationStateChanged(); + } + } + } + } + } + }; protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { @Override @@ -321,11 +342,21 @@ public class NotificationLockscreenUserManagerImpl implements mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN); dumpManager.registerDumpable(this); + + if (keyguardPrivateNotifications()) { + init(); + } } public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; + if (!keyguardPrivateNotifications()) { + init(); + } + } + + private void init() { mLockscreenSettingsObserver = new ContentObserver( mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) ? mBackgroundHandler @@ -408,6 +439,11 @@ public class NotificationLockscreenUserManagerImpl implements new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) ? mBackgroundExecutor : null, UserHandle.ALL); + if (keyguardPrivateNotifications()) { + mBroadcastDispatcher.registerReceiver(mKeyguardReceiver, + new IntentFilter(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED), + mBackgroundExecutor, UserHandle.ALL); + } IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); @@ -449,6 +485,10 @@ public class NotificationLockscreenUserManagerImpl implements mLockscreenSettingsObserver.onChange( false, mLockScreenUris, 0, UserHandle.of(userId)); updateDpcSettings(userId); + + if (keyguardPrivateNotifications()) { + updateGlobalKeyguardSettings(); + } } public boolean shouldShowLockscreenNotifications() { @@ -477,8 +517,12 @@ public class NotificationLockscreenUserManagerImpl implements boolean allowedByDpm; if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { - show = mUsersUsersAllowingNotifications.get(mCurrentUserId) - && mKeyguardAllowingNotifications; + if (keyguardPrivateNotifications()) { + show = mUsersUsersAllowingNotifications.get(mCurrentUserId); + } else { + show = mUsersUsersAllowingNotifications.get(mCurrentUserId) + && mKeyguardAllowingNotifications; + } // If DPC never notified us about a user, that means they have no policy for the user, // and they allow the behavior allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true); @@ -521,8 +565,13 @@ public class NotificationLockscreenUserManagerImpl implements 1, userId) != 0; mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen); - boolean keyguardChanged = updateGlobalKeyguardSettings(); - return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged; + + if (keyguardPrivateNotifications()) { + return (newAllowLockscreen != originalAllowLockscreen); + } else { + boolean keyguardChanged = updateGlobalKeyguardSettings(); + return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged; + } } @WorkerThread @@ -560,8 +609,14 @@ public class NotificationLockscreenUserManagerImpl implements Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable()); return false; } - return mUsersUsersAllowingPrivateNotifications.get(userHandle) - && mUsersDpcAllowingPrivateNotifications.get(userHandle); + if (keyguardPrivateNotifications()) { + return mUsersUsersAllowingPrivateNotifications.get(userHandle) + && mUsersDpcAllowingPrivateNotifications.get(userHandle) + && mKeyguardAllowingNotifications; + } else { + return mUsersUsersAllowingPrivateNotifications.get(userHandle) + && mUsersDpcAllowingPrivateNotifications.get(userHandle); + } } else { if (userHandle == USER_ALL) { return true; @@ -648,9 +703,14 @@ public class NotificationLockscreenUserManagerImpl implements Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable()); updateDpcSettings(userHandle); } - return mUsersUsersAllowingNotifications.get(userHandle) - && mUsersDpcAllowingNotifications.get(userHandle) - && mKeyguardAllowingNotifications; + if (keyguardPrivateNotifications()) { + return mUsersUsersAllowingNotifications.get(userHandle) + && mUsersDpcAllowingNotifications.get(userHandle); + } else { + return mUsersUsersAllowingNotifications.get(userHandle) + && mUsersDpcAllowingNotifications.get(userHandle) + && mKeyguardAllowingNotifications; + } } else { if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { return true; @@ -689,7 +749,12 @@ public class NotificationLockscreenUserManagerImpl implements ent.getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE; boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey()); - return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted; + if (keyguardPrivateNotifications()) { + return !mKeyguardAllowingNotifications + || userForcesRedaction || notificationRequestsRedaction && isNotifRedacted; + } else { + return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted; + } } private boolean packageHasVisibilityOverride(String key) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index 3f76d303898e..8858d132c4be 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -51,6 +51,7 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import dagger.Lazy @SmallTest class ActiveUnlockConfigTest : SysuiTestCase() { @@ -60,6 +61,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var lazyKeyguardUpdateMonitor: Lazy<KeyguardUpdateMonitor> @Mock private lateinit var mockPrintWriter: PrintWriter @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> @@ -72,6 +74,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUser) + whenever(lazyKeyguardUpdateMonitor.get()).thenReturn(keyguardUpdateMonitor) secureSettings = FakeSettings() activeUnlockConfig = ActiveUnlockConfig( @@ -79,6 +82,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings, contentResolver, selectedUserInteractor, + lazyKeyguardUpdateMonitor, dumpManager ) } @@ -260,7 +264,6 @@ class ActiveUnlockConfigTest : SysuiTestCase() { updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // GIVEN fingerprint and face are NOT enrolled - activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false) `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false) @@ -290,7 +293,6 @@ class ActiveUnlockConfigTest : SysuiTestCase() { updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // GIVEN fingerprint and face are both enrolled - activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true) `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index 7f20d9aa9451..51ceda339778 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -37,11 +37,11 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -98,7 +98,6 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { .thenReturn(mKeyguardMessageArea); when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources()); mFeatureFlags = new FakeFeatureFlags(); - mFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false); mKeyguardAbsKeyInputViewController = createTestObject(); mKeyguardAbsKeyInputViewController.init(); reset(mKeyguardMessageAreaController); // Clear out implicit call to init. @@ -127,7 +126,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { @Test public void withFeatureFlagOn_oldMessage_isHidden() { - mFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true); + mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES); KeyguardAbsKeyInputViewController underTest = createTestObject(); underTest.init(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 1ab634c46de9..d03a898cf6d1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -2283,6 +2283,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { Optional.of(mInteractiveToAuthProvider), mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); + start(); } public boolean hasSimStateJustChanged() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt new file mode 100644 index 000000000000..202d9ce27a34 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2023 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 + +import android.os.Looper +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.dagger.GlobalRootComponent +import com.android.systemui.dagger.SysUIComponent +import com.android.systemui.dump.dumpManager +import com.android.systemui.flags.systemPropertiesHelper +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.process.processWrapper +import com.android.systemui.startable.Dependencies +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import javax.inject.Provider +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(AndroidJUnit4::class) +@SmallTest +@RunWithLooper +class SystemUIApplicationTest : SysuiTestCase() { + + private val app: SystemUIApplication = SystemUIApplication() + private lateinit var contextAvailableCallback: + SystemUIAppComponentFactoryBase.ContextAvailableCallback + + @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) + + val kosmos = Kosmos() + @Mock private lateinit var initializer: SystemUIInitializer + @Mock private lateinit var rootComponent: GlobalRootComponent + @Mock private lateinit var sysuiComponent: SysUIComponent + @Mock private lateinit var bootCompleteCache: BootCompleteCacheImpl + @Mock private lateinit var initController: InitController + + private val startableA = StartableA() + private val startableB = StartableB() + private val startableC = StartableC() + private val startableD = StartableD() + private val startableE = StartableE() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + app.attachBaseContext(context) + contextAvailableCallback = + SystemUIAppComponentFactoryBase.ContextAvailableCallback { initializer } + whenever(initializer.rootComponent).thenReturn(rootComponent) + whenever(initializer.sysUIComponent).thenReturn(sysuiComponent) + whenever(rootComponent.mainLooper).thenReturn(Looper.myLooper()) + whenever(rootComponent.systemPropertiesHelper).thenReturn(kosmos.systemPropertiesHelper) + whenever(rootComponent.processWrapper).thenReturn(kosmos.processWrapper) + whenever(sysuiComponent.provideBootCacheImpl()).thenReturn(bootCompleteCache) + whenever(sysuiComponent.createDumpManager()).thenReturn(kosmos.dumpManager) + whenever(sysuiComponent.initController).thenReturn(initController) + kosmos.processWrapper.systemUser = true + + app.setContextAvailableCallback(contextAvailableCallback) + } + + @Test + fun testAppOnCreate() { + app.onCreate() + } + + @Test + fun testStartServices_singleService() { + whenever(sysuiComponent.startables) + .thenReturn(mutableMapOf(StartableA::class.java to Provider { startableA })) + app.onCreate() + app.startServicesIfNeeded() + assertThat(startableA.started).isTrue() + } + + @Test + fun testStartServices_twoServices() { + whenever(sysuiComponent.startables) + .thenReturn( + mutableMapOf( + StartableA::class.java to Provider { startableA }, + StartableB::class.java to Provider { startableB } + ) + ) + app.onCreate() + app.startServicesIfNeeded() + assertThat(startableA.started).isTrue() + assertThat(startableB.started).isTrue() + } + + @Test + fun testStartServices_simpleDependency() { + whenever(sysuiComponent.startables) + .thenReturn( + mutableMapOf( + StartableC::class.java to Provider { startableC }, + StartableA::class.java to Provider { startableA }, + StartableB::class.java to Provider { startableB } + ) + ) + app.onCreate() + app.startServicesIfNeeded() + assertThat(startableA.started).isTrue() + assertThat(startableB.started).isTrue() + assertThat(startableC.started).isTrue() + assertThat(startableC.order).isGreaterThan(startableA.order) + } + + @Test + fun testStartServices_complexDependency() { + whenever(sysuiComponent.startables) + .thenReturn( + mutableMapOf( + StartableE::class.java to Provider { startableE }, + StartableC::class.java to Provider { startableC }, + StartableD::class.java to Provider { startableD }, + StartableA::class.java to Provider { startableA }, + StartableB::class.java to Provider { startableB } + ) + ) + app.onCreate() + app.startServicesIfNeeded() + assertThat(startableA.started).isTrue() + assertThat(startableB.started).isTrue() + assertThat(startableC.started).isTrue() + assertThat(startableD.started).isTrue() + assertThat(startableE.started).isTrue() + assertThat(startableC.order).isGreaterThan(startableA.order) + assertThat(startableD.order).isGreaterThan(startableA.order) + assertThat(startableD.order).isGreaterThan(startableB.order) + assertThat(startableE.order).isGreaterThan(startableB.order) + assertThat(startableE.order).isGreaterThan(startableD.order) + } + + open class TestableStartable : CoreStartable { + companion object { + var startOrder = 0 + } + + var started = false + var order = -1 + + override fun start() { + started = true + order = startOrder + startOrder++ + } + } + + class StartableA : TestableStartable() + class StartableB : TestableStartable() + + @Dependencies(StartableA::class) class StartableC : TestableStartable() + + @Dependencies(StartableA::class, StartableB::class) class StartableD : TestableStartable() + + @Dependencies(StartableD::class, StartableB::class) class StartableE : TestableStartable() +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt index aa0d7b621793..45a426e1cb84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt @@ -25,6 +25,7 @@ import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FaceSensorInfo import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository @@ -35,8 +36,6 @@ import com.android.systemui.bouncer.shared.model.BouncerMessageModel import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository @@ -107,8 +106,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { suspend fun TestScope.init() { userRepository.setSelectedUserInfo(PRIMARY_USER) - val featureFlags = - FakeFeatureFlagsClassic().apply { set(Flags.REVAMPED_BOUNCER_MESSAGES, true) } + mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) primaryBouncerInteractor = PrimaryBouncerInteractor( bouncerRepository, @@ -131,7 +129,6 @@ class BouncerMessageInteractorTest : SysuiTestCase() { repository = repository, userRepository = userRepository, countDownTimerUtil = countDownTimerUtil, - featureFlags = featureFlags, updateMonitor = updateMonitor, biometricSettingsRepository = biometricSettingsRepository, applicationScope = this.backgroundScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt index dc0d9c5f987e..070a0ccd1232 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt @@ -17,21 +17,26 @@ package com.android.systemui.keyguard.ui.view.layout.sections +import android.content.pm.PackageManager +import android.content.res.Resources import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.Utils +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -40,17 +45,10 @@ import org.mockito.MockitoAnnotations class ClockSectionTest : SysuiTestCase() { @Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel - @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel @Mock private lateinit var splitShadeStateController: SplitShadeStateController private lateinit var underTest: ClockSection - // smartspaceViewModel.getDimen("date_weather_view_height") - private val SMART_SPACE_DATE_WEATHER_HEIGHT = 10 - - // smartspaceViewModel.getDimen("enhanced_smartspace_height") - private val ENHANCED_SMART_SPACE_HEIGHT = 11 - private val SMALL_CLOCK_TOP_SPLIT_SHADE = context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) @@ -75,15 +73,41 @@ class ClockSectionTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - whenever(smartspaceViewModel.getDimen("date_weather_view_height")) - .thenReturn(SMART_SPACE_DATE_WEATHER_HEIGHT) - whenever(smartspaceViewModel.getDimen("enhanced_smartspace_height")) - .thenReturn(ENHANCED_SMART_SPACE_HEIGHT) + val remoteResources = mock<Resources>() + whenever( + remoteResources.getIdentifier( + anyString(), + eq("dimen"), + anyString(), + ) + ) + .then { invocation -> + val name = invocation.arguments[0] as String + val index = DIMENSION_BY_IDENTIFIER_NAME.indexOfFirst { (key, _) -> key == name } + if (index == -1) { + error( + "No entry for a dimension named \"$name\", please add it to the list above." + ) + } + index + } + whenever( + remoteResources.getDimensionPixelSize( + anyInt(), + ) + ) + .then { invocation -> + val id = invocation.arguments[0] as Int + DIMENSION_BY_IDENTIFIER_NAME[id].second + } + val packageManager = mock<PackageManager>() + whenever(packageManager.getResourcesForApplication(anyString())).thenReturn(remoteResources) + mContext.setMockPackageManager(packageManager) + underTest = ClockSection( keyguardClockInteractor, keyguardClockViewModel, - smartspaceViewModel, mContext, splitShadeStateController, ) @@ -164,4 +188,14 @@ class ClockSectionTest : SysuiTestCase() { assertThat(smallClockConstraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) assertThat(smallClockConstraint.layout.topMargin).isEqualTo(expectedSmallClockTopMargin) } + + companion object { + private val SMART_SPACE_DATE_WEATHER_HEIGHT = 10 + private val ENHANCED_SMART_SPACE_HEIGHT = 11 + private val DIMENSION_BY_IDENTIFIER_NAME = + listOf( + "date_weather_view_height" to SMART_SPACE_DATE_WEATHER_HEIGHT, + "enhanced_smartspace_height" to ENHANCED_SMART_SPACE_HEIGHT, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt index 740110b4fee0..28da957d31b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt @@ -29,7 +29,9 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R +import com.android.systemui.shared.R as sharedR import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.StateFlow @@ -50,9 +52,9 @@ class SmartspaceSectionTest : SysuiTestCase() { @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock private lateinit var hasCustomWeatherDataDisplay: StateFlow<Boolean> - private val smartspaceView = View(mContext).also { it.id = View.generateViewId() } - private val weatherView = View(mContext).also { it.id = View.generateViewId() } - private val dateView = View(mContext).also { it.id = View.generateViewId() } + private val smartspaceView = View(mContext).also { it.id = sharedR.id.bc_smartspace_view } + private val weatherView = View(mContext).also { it.id = sharedR.id.weather_smartspace_view } + private val dateView = View(mContext).also { it.id = sharedR.id.date_smartspace_view } private lateinit var constraintLayout: ConstraintLayout private lateinit var constraintSet: ConstraintSet @@ -69,13 +71,13 @@ class SmartspaceSectionTest : SysuiTestCase() { keyguardUnlockAnimationController, ) constraintLayout = ConstraintLayout(mContext) - whenever(keyguardSmartspaceViewModel.smartspaceView).thenReturn(smartspaceView) - whenever(keyguardSmartspaceViewModel.weatherView).thenReturn(weatherView) - whenever(keyguardSmartspaceViewModel.dateView).thenReturn(dateView) + whenever(lockscreenSmartspaceController.buildAndConnectView(any())) + .thenReturn(smartspaceView) + whenever(lockscreenSmartspaceController.buildAndConnectWeatherView(any())) + .thenReturn(weatherView) + whenever(lockscreenSmartspaceController.buildAndConnectDateView(any())).thenReturn(dateView) whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay) .thenReturn(hasCustomWeatherDataDisplay) - whenever(keyguardSmartspaceViewModel.smartspaceController) - .thenReturn(lockscreenSmartspaceController) constraintSet = ConstraintSet() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 86d8d54684ac..1e1e34421a57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -51,7 +51,6 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED -import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES @@ -197,9 +196,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true) featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false) featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) - featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true) featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) testScope = TestScope() fakeClock = FakeSystemClock() @@ -242,7 +241,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { repository = BouncerMessageRepositoryImpl(), userRepository = FakeUserRepository(), countDownTimerUtil = mock(CountDownTimerUtil::class.java), - featureFlags = featureFlagsClassic, updateMonitor = mock(KeyguardUpdateMonitor::class.java), biometricSettingsRepository = FakeBiometricSettingsRepository(), applicationScope = testScope.backgroundScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index d9ff892145c7..dd4ac9d28a9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl @@ -189,9 +190,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true) featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false) featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) - featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES) testScope = TestScope() controller = NotificationShadeWindowViewController( @@ -232,7 +233,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { repository = BouncerMessageRepositoryImpl(), userRepository = FakeUserRepository(), countDownTimerUtil = Mockito.mock(CountDownTimerUtil::class.java), - featureFlags = featureFlags, updateMonitor = Mockito.mock(KeyguardUpdateMonitor::class.java), biometricSettingsRepository = FakeBiometricSettingsRepository(), applicationScope = testScope.backgroundScope, 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 42c7375bfb2e..0c6f456b1e80 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -19,9 +19,12 @@ package com.android.systemui.statusbar; import static android.app.Notification.VISIBILITY_PRIVATE; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; +import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; +import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS; import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.os.UserHandle.USER_ALL; import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; @@ -111,7 +114,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return FlagsParameterization.allCombinationsOf(FLAG_ALLOW_PRIVATE_PROFILE); + return FlagsParameterization.allCombinationsOf( + FLAG_ALLOW_PRIVATE_PROFILE, + FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS); } public NotificationLockscreenUserManagerTest(FlagsParameterization flags) { @@ -245,6 +250,19 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS) + public void testInit() { + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); + mLockscreenUserManager.setUpWithPresenter(mPresenter); + + mBackgroundExecutor.runAllReady(); + + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + } + + @Test public void testGetCurrentProfiles() { final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>(); expectedCurProfiles.put(mCurrentUser.id, mCurrentUser); @@ -579,6 +597,29 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS) + public void testKeyguardManager_noPrivateNotifications() { + Mockito.clearInvocations(mDevicePolicyManager); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED) + .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true)); + + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + // it's a global field, confirm secondary too + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); + assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( + mSecondaryUser.id)); + } + + @Test public void testDevicePolicy_noPrivateNotifications() { Mockito.clearInvocations(mDevicePolicyManager); // User allows notifications @@ -699,6 +740,29 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS) + public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications_show() { + // KeyguardManager does not allow notifications + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + // DevicePolicy allows notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(0); + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, mCurrentUser.id, 0); + mLockscreenUserManager.mKeyguardReceiver.setPendingResult(pr); + mLockscreenUserManager.mKeyguardReceiver.onReceive(mContext, + new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED) + .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false)); + + assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS) public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() { // KeyguardManager does not allow notifications when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); @@ -718,6 +782,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS) public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { // DevicePolicy allows notifications when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessKosmos.kt new file mode 100644 index 000000000000..79167f840f60 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 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.process + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.processWrapper: ProcessWrapperFake by Kosmos.Fixture { ProcessWrapperFake() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt new file mode 100644 index 000000000000..9841778f835b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 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.process + +class ProcessWrapperFake : ProcessWrapper() { + + var systemUser: Boolean = false + + override fun isSystemUser(): Boolean { + return systemUser + } +} diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index 3a90a959b877..73ed97ff7b11 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -35,8 +35,8 @@ import android.service.contentcapture.SnapshotData; import android.util.LocalLog; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; +import android.view.contentcapture.ContentCaptureSession; import android.view.contentcapture.ContentCaptureSessionId; -import android.view.contentcapture.MainContentCaptureSession; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; @@ -123,7 +123,7 @@ final class ContentCaptureServerSession { public void setContentCaptureEnabledLocked(boolean enabled) { try { final Bundle extras = new Bundle(); - extras.putBoolean(MainContentCaptureSession.EXTRA_ENABLED_STATE, true); + extras.putBoolean(ContentCaptureSession.EXTRA_ENABLED_STATE, true); mSessionStateReceiver.send(enabled ? RESULT_CODE_TRUE : RESULT_CODE_FALSE, extras); } catch (RemoteException e) { Slog.w(TAG, "Error async reporting result to client: " + e); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 96b1650d9575..02f4485d5b40 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2118,11 +2118,6 @@ public final class ActiveServices { // anyway, so we just remove the SHORT_SERVICE type. foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; } - if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) { - throw new ForegroundServiceStartNotAllowedException("FGS type " - + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType) - + " not allowed to start from BOOT_COMPLETED!"); - } boolean alreadyStartedOp = false; boolean stopProcStatsOp = false; @@ -2137,6 +2132,12 @@ public final class ActiveServices { mServiceFGAnrTimer.cancel(r); } + if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) { + throw new ForegroundServiceStartNotAllowedException("FGS type " + + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType) + + " not allowed to start from BOOT_COMPLETED!"); + } + final ProcessServiceRecord psr = r.app.mServices; try { boolean ignoreForeground = false; diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java index 4df25811cc99..5d609bca334c 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java @@ -54,8 +54,8 @@ public class AuthenticationStatsCollector { @NonNull private final Context mContext; @NonNull private final PackageManager mPackageManager; - @NonNull private final FaceManager mFaceManager; - @NonNull private final FingerprintManager mFingerprintManager; + @Nullable private final FaceManager mFaceManager; + @Nullable private final FingerprintManager mFingerprintManager; private final boolean mEnabled; private final float mThreshold; @@ -197,11 +197,11 @@ public class AuthenticationStatsCollector { } private boolean hasEnrolledFace(int userId) { - return mFaceManager.hasEnrolledTemplates(userId); + return mFaceManager != null && mFaceManager.hasEnrolledTemplates(userId); } private boolean hasEnrolledFingerprint(int userId) { - return mFingerprintManager.hasEnrolledTemplates(userId); + return mFingerprintManager != null && mFingerprintManager.hasEnrolledTemplates(userId); } /** diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java index 0eb9166371dc..5c9e61a2940b 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java @@ -58,11 +58,9 @@ import java.util.Objects; * <p>This implementation obtains and manages all routes via {@link AudioManager}, with the * exception of {@link AudioManager#handleBluetoothActiveDeviceChanged inactive bluetooth} routes * which are managed by {@link AudioPoliciesBluetoothRouteController}, which depends on the - * bluetooth stack (for example {@link BluetoothAdapter}. + * bluetooth stack ({@link BluetoothAdapter} and related classes). */ -// TODO: b/305199571 - Rename this class to avoid the AudioPolicies prefix, which has been flagged -// by the audio team as a confusing name. -/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController { +/* package */ final class AudioManagerRouteController implements DeviceRouteController { private static final String TAG = SystemMediaRoute2Provider.TAG; @NonNull @@ -103,7 +101,7 @@ import java.util.Objects; Manifest.permission.MODIFY_AUDIO_ROUTING, Manifest.permission.QUERY_AUDIO_STATE }) - /* package */ AudioPoliciesDeviceRouteController( + /* package */ AudioManagerRouteController( @NonNull Context context, @NonNull AudioManager audioManager, @NonNull Looper looper, diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index 8b62cc974862..dff0adfca370 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -65,7 +65,7 @@ import java.util.List; if (strategyForMedia != null && btAdapter != null && Flags.enableAudioPoliciesDeviceAndBluetoothController()) { - return new AudioPoliciesDeviceRouteController( + return new AudioManagerRouteController( context, audioManager, looper, diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING index 1b49093a66d8..b3e5b9e96889 100644 --- a/services/core/java/com/android/server/media/TEST_MAPPING +++ b/services/core/java/com/android/server/media/TEST_MAPPING @@ -3,5 +3,10 @@ { "name": "CtsMediaBetterTogetherTestCases" } + ], + "postsubmit": [ + { + "name": "MediaRouterServiceTests" + } ] } diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 85731651dd59..4d19eade5a05 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -42,6 +42,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.Keep; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.server.am.ProcessList; @@ -693,6 +694,7 @@ public class NetworkPolicyLogger { * Note: This class needs to be public for RingBuffer class to be able to create * new instances of this. */ + @Keep public static final class Data { public int type; public long timeStamp; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ff415c155b35..d9e6692e95b3 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -16,7 +16,9 @@ package com.android.server.notification; +import static android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS; import static android.Manifest.permission.RECEIVE_SENSITIVE_NOTIFICATIONS; +import static android.Manifest.permission.STATUS_BAR_SERVICE; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE; import static android.app.AppOpsManager.MODE_ALLOWED; @@ -67,6 +69,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OF import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static android.app.Flags.lifetimeExtensionRefactor; +import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; +import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; @@ -210,6 +214,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; @@ -3369,9 +3374,7 @@ public class NotificationManagerService extends SystemService { .setChannelName(r.getChannel().getName().toString()) .setPostedTimeMs(System.currentTimeMillis()) .setTitle(getHistoryTitle(r.getNotification())) - .setText(getHistoryText( - r.getSbn().getPackageContext(getContext()), - r.getNotification())) + .setText(getHistoryText(r.getNotification())) .setIcon(r.getNotification().getSmallIcon()) .build()); } @@ -3414,12 +3417,11 @@ public class NotificationManagerService extends SystemService { /** * Returns the appropriate substring for this notification based on the style of notification. */ - private String getHistoryText(Context appContext, Notification n) { + private String getHistoryText(Notification n) { CharSequence text = null; if (n.extras != null) { text = n.extras.getCharSequence(EXTRA_TEXT); - - Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, n); + Notification.Builder nb = Notification.Builder.recoverBuilder(getContext(), n); if (nb.getStyle() instanceof Notification.BigTextStyle) { text = ((Notification.BigTextStyle) nb.getStyle()).getBigText(); @@ -5126,8 +5128,10 @@ public class NotificationManagerService extends SystemService { for (int userId : mUm.getProfileIds(info.userid, false)) { try { int uid = getUidForPackageAndUser(pkg, UserHandle.of(userId)); - VersionedPackage vp = new VersionedPackage(pkg, uid); - nlf.addPackage(vp); + if (uid != INVALID_UID) { + VersionedPackage vp = new VersionedPackage(pkg, uid); + nlf.addPackage(vp); + } } catch (Exception e) { // pkg doesn't exist on that user; skip } @@ -5569,7 +5573,7 @@ public class NotificationManagerService extends SystemService { private void enforceSystemOrSystemUI(String message) { if (isCallerSystemOrPhone()) return; - getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + getContext().enforceCallingPermission(STATUS_BAR_SERVICE, message); } @@ -5578,7 +5582,7 @@ public class NotificationManagerService extends SystemService { checkCallerIsSystemOrSameApp(pkg); } catch (SecurityException e) { getContext().enforceCallingPermission( - android.Manifest.permission.STATUS_BAR_SERVICE, + STATUS_BAR_SERVICE, message); } } @@ -6183,13 +6187,20 @@ public class NotificationManagerService extends SystemService { @Override public void setPrivateNotificationsAllowed(boolean allow) { if (PackageManager.PERMISSION_GRANTED - != getContext().checkCallingPermission( - permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) { + != getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) { throw new SecurityException( "Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission"); } if (allow != mLockScreenAllowSecureNotifications) { mLockScreenAllowSecureNotifications = allow; + if (android.app.Flags.keyguardPrivateNotifications()) { + getContext().sendBroadcast( + new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED) + .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, + mLockScreenAllowSecureNotifications), + STATUS_BAR_SERVICE); + } + handleSavePolicyFile(); } } @@ -6197,8 +6208,7 @@ public class NotificationManagerService extends SystemService { @Override public boolean getPrivateNotificationsAllowed() { if (PackageManager.PERMISSION_GRANTED - != getContext().checkCallingPermission( - permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) { + != getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) { throw new SecurityException( "Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission"); } @@ -8369,6 +8379,8 @@ public class NotificationManagerService extends SystemService { boolean posted = false; try { posted = postNotification(); + } catch (Exception e) { + Slog.e(TAG, "Error posting", e); } finally { if (!posted) { mTracker.cancel(); @@ -9557,12 +9569,16 @@ public class NotificationManagerService extends SystemService { } private void scheduleListenerHintsChanged(int state) { - mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED); + if (!Flags.notificationReduceMessagequeueUsage()) { + mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED); + } mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget(); } private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) { - mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED); + if (!Flags.notificationReduceMessagequeueUsage()) { + mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED); + } mHandler.obtainMessage( MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED, listenerInterruptionFilter, @@ -9642,15 +9658,24 @@ public class NotificationManagerService extends SystemService { } protected void scheduleSendRankingUpdate() { - if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) { + if (Flags.notificationReduceMessagequeueUsage()) { Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE); sendMessage(m); + } else { + if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) { + Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE); + sendMessage(m); + } } } protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable) { - if (!hasCallbacks(cancelRunnable)) { + if (Flags.notificationReduceMessagequeueUsage()) { sendMessage(Message.obtain(this, cancelRunnable)); + } else { + if (!hasCallbacks(cancelRunnable)) { + sendMessage(Message.obtain(this, cancelRunnable)); + } } } @@ -9684,7 +9709,9 @@ public class NotificationManagerService extends SystemService { } public void requestSort() { - removeMessages(MESSAGE_RANKING_SORT); + if (!Flags.notificationReduceMessagequeueUsage()) { + removeMessages(MESSAGE_RANKING_SORT); + } Message msg = Message.obtain(); msg.what = MESSAGE_RANKING_SORT; sendMessage(msg); @@ -10589,7 +10616,7 @@ public class NotificationManagerService extends SystemService { if (isCallerSystemOrPhone()) { return true; } - return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE) + return getContext().checkCallingPermission(STATUS_BAR_SERVICE) == PERMISSION_GRANTED; } @@ -10628,7 +10655,7 @@ public class NotificationManagerService extends SystemService { if (isCallerSystemOrPhone()) { return; } - getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + getContext().enforceCallingPermission(STATUS_BAR_SERVICE, message); } diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java index df570a02eba5..0145577fb945 100644 --- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java +++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java @@ -23,6 +23,7 @@ import static android.service.notification.NotificationServiceProto.RULE_TYPE_MA import static android.service.notification.NotificationServiceProto.RULE_TYPE_UNKNOWN; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Flags; import android.app.NotificationManager; import android.content.pm.PackageManager; @@ -256,13 +257,21 @@ class ZenModeEventLogger { return true; } + if (Flags.modesApi() && hasActiveRuleCountDiff()) { + // Rules with INTERRUPTION_FILTER_ALL were always possible but before MODES_API + // they were completely useless; now they can apply effects, so we want to log + // when they become active/inactive, even though DND itself (as in "notification + // blocking") is off. + return true; + } + // If zen mode didn't change, did the policy or number of active rules change? We only // care about changes that take effect while zen mode is on, so make sure the current // zen mode is not "OFF" if (mNewZenMode == ZEN_MODE_OFF) { return false; } - return hasPolicyDiff() || hasRuleCountDiff(); + return hasPolicyDiff() || hasActiveRuleCountDiff(); } // Does the difference in zen mode go from off to on or vice versa? @@ -294,6 +303,16 @@ class ZenModeEventLogger { } } + if (Flags.modesApi() && mNewZenMode == ZEN_MODE_OFF) { + // If the mode is OFF -> OFF then there cannot be any *effective* change to policy. + // (Note that, in theory, a policy diff is impossible since we don't merge the + // policies of INTERRUPTION_FILTER_ALL rules; this is a "just in case" check). + if (hasPolicyDiff() || hasChannelsBypassingDiff()) { + Log.wtf(TAG, "Detected policy diff even though DND is OFF and not toggled"); + } + return ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED; + } + // zen mode didn't change; we must be here because of a policy change or rule change if (hasPolicyDiff() || hasChannelsBypassingDiff()) { return ZenStateChangedEvent.DND_POLICY_CHANGED; @@ -345,7 +364,7 @@ class ZenModeEventLogger { * Returns whether the previous config and new config have a different number of active * automatic or manual rules. */ - private boolean hasRuleCountDiff() { + private boolean hasActiveRuleCountDiff() { return numActiveRulesInConfig(mPrevConfig) != numActiveRulesInConfig(mNewConfig); } @@ -381,9 +400,11 @@ class ZenModeEventLogger { // Determine the number of (automatic & manual) rules active after the change takes place. int getNumRulesActive() { - // If the zen mode has turned off, that means nothing can be active. - if (mNewZenMode == ZEN_MODE_OFF) { - return 0; + if (!Flags.modesApi()) { + // If the zen mode has turned off, that means nothing can be active. + if (mNewZenMode == ZEN_MODE_OFF) { + return 0; + } } return numActiveRulesInConfig(mNewConfig); } @@ -478,8 +499,19 @@ class ZenModeEventLogger { /** * Convert the new policy to a DNDPolicyProto format for output in logs. + * + * <p>If {@code mNewZenMode} is {@code ZEN_MODE_OFF} (which can mean either no rules + * active, or only rules with {@code INTERRUPTION_FILTER_ALL} active) then this returns + * {@code null} (which will be mapped to a missing submessage in the proto). Although this + * is not the value of {@code NotificationManager#getConsolidatedNotificationPolicy()}, it + * makes sense for logging since that policy is not actually influencing anything. */ + @Nullable byte[] getDNDPolicyProto() { + if (Flags.modesApi() && mNewZenMode == ZEN_MODE_OFF) { + return null; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ProtoOutputStream proto = new ProtoOutputStream(bytes); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 0a46901a93d1..b7a203814c94 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1490,7 +1490,12 @@ public class ZenModeHelper { for (ZenRule automaticRule : mConfig.automaticRules.values()) { if (automaticRule.isAutomaticActive()) { - applyCustomPolicy(policy, automaticRule); + // Active rules with INTERRUPTION_FILTER_ALL are not included in consolidated + // policy. This is relevant in case some other active rule has a more + // restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy! + if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) { + applyCustomPolicy(policy, automaticRule); + } if (Flags.modesApi()) { deviceEffectsBuilder.add(automaticRule.zenDeviceEffects); } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 8e79922a996a..49db7fc280d9 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -50,3 +50,10 @@ flag { # Referenced in WM where WM starts before DeviceConfig is_fixed_read_only: true } + +flag { + name: "notification_reduce_messagequeue_usage" + namespace: "systemui" + description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler" + bug: "311051285" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5b13d3fead90..edce3ec4b37c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -473,6 +473,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { private TalkbackShortcutController mTalkbackShortcutController; + private WindowWakeUpPolicy mWindowWakeUpPolicy; + boolean mSafeMode; // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. @@ -640,15 +642,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Whether to lock the device after the next dreaming transition has finished. private boolean mLockAfterDreamingTransitionFinished; - // Allowed theater mode wake actions - private boolean mAllowTheaterModeWakeFromKey; - private boolean mAllowTheaterModeWakeFromPowerKey; - private boolean mAllowTheaterModeWakeFromMotion; - private boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming; - private boolean mAllowTheaterModeWakeFromCameraLens; - private boolean mAllowTheaterModeWakeFromLidSwitch; - private boolean mAllowTheaterModeWakeFromWakeGesture; - // If true, the power button long press behavior will be invoked even if the default display is // non-interactive. If false, the power button long press behavior will be skipped if the // default display is non-interactive. @@ -930,8 +923,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (shouldEnableWakeGestureLp()) { performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false, "Wake Up"); - wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture, - PowerManager.WAKE_REASON_GESTURE, "android.policy:GESTURE"); + mWindowWakeUpPolicy.wakeUpFromWakeGesture(); } } } @@ -1067,7 +1059,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted(); if (!mPowerKeyHandled) { if (!interactive) { - wakeUpFromPowerKey(event.getDownTime()); + wakeUpFromWakeKey(event); } } else { // handled by another power key policy. @@ -1309,7 +1301,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0); if (!interactive) { - wakeUpFromPowerKey(eventTime); + wakeUpFromWakeKey(eventTime, KEYCODE_POWER); } } else { Slog.i(TAG, "Toggling theater mode on."); @@ -1325,7 +1317,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MULTI_PRESS_POWER_BRIGHTNESS_BOOST: Slog.i(TAG, "Starting brightness boost."); if (!interactive) { - wakeUpFromPowerKey(eventTime); + wakeUpFromWakeKey(eventTime, KEYCODE_POWER); } mPowerManager.boostScreenBrightness(eventTime); break; @@ -2312,22 +2304,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mLidNavigationAccessibility = mContext.getResources().getInteger( com.android.internal.R.integer.config_lidNavigationAccessibility); - mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_allowTheaterModeWakeFromKey); - mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey - || mContext.getResources().getBoolean( - com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey); - mAllowTheaterModeWakeFromMotion = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion); - mAllowTheaterModeWakeFromMotionWhenNotDreaming = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming); - mAllowTheaterModeWakeFromCameraLens = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens); - mAllowTheaterModeWakeFromLidSwitch = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch); - mAllowTheaterModeWakeFromWakeGesture = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture); - mGoToSleepOnButtonPressTheaterMode = mContext.getResources().getBoolean( com.android.internal.R.bool.config_goToSleepOnButtonPressTheaterMode); @@ -2457,6 +2433,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.integer.config_keyguardDrawnTimeout); mKeyguardDelegate = injector.getKeyguardServiceDelegate(); mTalkbackShortcutController = injector.getTalkbackShortcutController(); + mWindowWakeUpPolicy = new WindowWakeUpPolicy(mContext); initKeyCombinationRules(); initSingleKeyGestureRules(injector.getLooper()); mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker(); @@ -4483,8 +4460,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { updateRotation(true); if (lidOpen) { - wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromLidSwitch, - PowerManager.WAKE_REASON_LID, "android.policy:LID"); + mWindowWakeUpPolicy.wakeUpFromLid(); } else if (getLidBehavior() != LID_BEHAVIOR_SLEEP) { mPowerManager.userActivity(SystemClock.uptimeMillis(), false); } @@ -4510,8 +4486,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else { intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); } - wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromCameraLens, - PowerManager.WAKE_REASON_CAMERA_LAUNCH, "android.policy:CAMERA_COVER"); + mWindowWakeUpPolicy.wakeUpFromCameraCover(whenNanos / 1000000); startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); } mCameraLensCoverState = lensCoverState; @@ -4589,7 +4564,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean shouldTurnOnTv = false; if (down && (keyCode == KeyEvent.KEYCODE_POWER || keyCode == KeyEvent.KEYCODE_TV_POWER)) { - wakeUpFromPowerKey(event.getDownTime()); + wakeUpFromWakeKey(event); shouldTurnOnTv = true; } else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP) && isWakeKeyWhenScreenOff(keyCode)) { @@ -5104,9 +5079,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mRequestedOrSleepingDefaultDisplay) { mCameraGestureTriggeredDuringGoingToSleep = true; // Wake device up early to prevent display doing redundant turning off/on stuff. - wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey, - PowerManager.WAKE_REASON_CAMERA_LAUNCH, - "android.policy:CAMERA_GESTURE_PREVENT_LOCK"); + mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture(); } return true; } @@ -5204,8 +5177,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action, long whenNanos, int policyFlags) { if ((policyFlags & FLAG_WAKE) != 0) { - if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion, - PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) { + if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) { // Woke up. Pass motion events to user. return ACTION_PASS_TO_USER; } @@ -5219,8 +5191,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // there will be no dream to intercept the touch and wake into ambient. The device should // wake up in this case. if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) { - if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming, - PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) { + if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) { // Woke up. Pass motion events to user. return ACTION_PASS_TO_USER; } @@ -5554,39 +5525,24 @@ public class PhoneWindowManager implements WindowManagerPolicy { return sleepDurationRealtime > mWakeUpToLastStateTimeout; } - private void wakeUpFromPowerKey(long eventTime) { - if (wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey, - PowerManager.WAKE_REASON_POWER_BUTTON, "android.policy:POWER")) { - // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout - if (shouldWakeUpWithHomeIntent()) { - startDockOrHome(DEFAULT_DISPLAY, /*fromHomeKey*/ false, /*wakenFromDreams*/ true, - PowerManager.wakeReasonToString(PowerManager.WAKE_REASON_POWER_BUTTON)); - } - } + private void wakeUpFromWakeKey(KeyEvent event) { + wakeUpFromWakeKey(event.getEventTime(), event.getKeyCode()); } - private void wakeUpFromWakeKey(KeyEvent event) { - if (wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey, - PowerManager.WAKE_REASON_WAKE_KEY, "android.policy:KEY")) { + private void wakeUpFromWakeKey(long eventTime, int keyCode) { + if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode)) { + final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER; // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout - if (shouldWakeUpWithHomeIntent() && event.getKeyCode() == KEYCODE_HOME) { - startDockOrHome(DEFAULT_DISPLAY, /*fromHomeKey*/ true, /*wakenFromDreams*/ true, - PowerManager.wakeReasonToString(PowerManager.WAKE_REASON_WAKE_KEY)); + if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) { + startDockOrHome( + DEFAULT_DISPLAY, + /*fromHomeKey*/ keyCode == KEYCODE_HOME, + /*wakenFromDreams*/ true, + "Wake from " + KeyEvent. keyCodeToString(keyCode)); } } } - private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason, - String details) { - final boolean theaterModeEnabled = isTheaterModeEnabled(); - if (!wakeInTheaterMode && theaterModeEnabled) { - return false; - } - - mPowerManager.wakeUp(wakeTime, reason, details); - return true; - } - private void finishKeyguardDrawn() { if (!mDefaultDisplayPolicy.finishKeyguardDrawn()) { return; diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java new file mode 100644 index 000000000000..392d0d4fdb52 --- /dev/null +++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static android.os.PowerManager.WAKE_REASON_CAMERA_LAUNCH; +import static android.os.PowerManager.WAKE_REASON_GESTURE; +import static android.os.PowerManager.WAKE_REASON_LID; +import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON; +import static android.os.PowerManager.WAKE_REASON_WAKE_KEY; +import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION; +import static android.view.KeyEvent.KEYCODE_POWER; + +import android.content.Context; +import android.content.res.Resources; +import android.os.PowerManager; +import android.os.PowerManager.WakeReason; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Slog; + + +/** Policy controlling the decision and execution of window-related wake ups. */ +class WindowWakeUpPolicy { + private static final String TAG = "WindowWakeUpPolicy"; + + private static final boolean DEBUG = false; + + private final Context mContext; + private final PowerManager mPowerManager; + + private final boolean mAllowTheaterModeWakeFromKey; + private final boolean mAllowTheaterModeWakeFromPowerKey; + private final boolean mAllowTheaterModeWakeFromMotion; + private final boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming; + private final boolean mAllowTheaterModeWakeFromCameraLens; + private final boolean mAllowTheaterModeWakeFromLidSwitch; + private final boolean mAllowTheaterModeWakeFromWakeGesture; + + WindowWakeUpPolicy(Context context) { + mContext = context; + mPowerManager = context.getSystemService(PowerManager.class); + + final Resources res = context.getResources(); + mAllowTheaterModeWakeFromKey = res.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromKey); + mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey + || res.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey); + mAllowTheaterModeWakeFromMotion = res.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion); + mAllowTheaterModeWakeFromMotionWhenNotDreaming = res.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming); + mAllowTheaterModeWakeFromCameraLens = res.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens); + mAllowTheaterModeWakeFromLidSwitch = res.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch); + mAllowTheaterModeWakeFromWakeGesture = res.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture); + } + + /** + * Wakes up from a key event. + * + * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}. + * @param keyCode the {@link android.view.KeyEvent} key code of the key event. + * @return {@code true} if the policy allows the requested wake up and the request has been + * executed; {@code false} otherwise. + */ + boolean wakeUpFromKey(long eventTime, int keyCode) { + final boolean wakeAllowedDuringTheaterMode = + keyCode == KEYCODE_POWER + ? mAllowTheaterModeWakeFromPowerKey + : mAllowTheaterModeWakeFromKey; + return wakeUp( + eventTime, + wakeAllowedDuringTheaterMode, + keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY, + keyCode == KEYCODE_POWER ? "POWER" : "KEY"); + } + + /** + * Wakes up from a motion event. + * + * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}. + * @return {@code true} if the policy allows the requested wake up and the request has been + * executed; {@code false} otherwise. + */ + boolean wakeUpFromMotion(long eventTime) { + return wakeUp( + eventTime, mAllowTheaterModeWakeFromMotion, WAKE_REASON_WAKE_MOTION, "MOTION"); + } + + /** + * Wakes up due to an opened camera cover. + * + * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}. + * @return {@code true} if the policy allows the requested wake up and the request has been + * executed; {@code false} otherwise. + */ + boolean wakeUpFromCameraCover(long eventTime) { + return wakeUp( + eventTime, + mAllowTheaterModeWakeFromCameraLens, + WAKE_REASON_CAMERA_LAUNCH, + "CAMERA_COVER"); + } + + /** + * Wakes up due to an opened lid. + * + * @return {@code true} if the policy allows the requested wake up and the request has been + * executed; {@code false} otherwise. + */ + boolean wakeUpFromLid() { + return wakeUp( + SystemClock.uptimeMillis(), + mAllowTheaterModeWakeFromLidSwitch, + WAKE_REASON_LID, + "LID"); + } + + /** + * Wakes up to prevent sleeping when opening camera through power button. + * + * @return {@code true} if the policy allows the requested wake up and the request has been + * executed; {@code false} otherwise. + */ + boolean wakeUpFromPowerKeyCameraGesture() { + return wakeUp( + SystemClock.uptimeMillis(), + mAllowTheaterModeWakeFromPowerKey, + WAKE_REASON_CAMERA_LAUNCH, + "CAMERA_GESTURE_PREVENT_LOCK"); + } + + /** + * Wake up from a wake gesture. + * + * @return {@code true} if the policy allows the requested wake up and the request has been + * executed; {@code false} otherwise. + */ + boolean wakeUpFromWakeGesture() { + return wakeUp( + SystemClock.uptimeMillis(), + mAllowTheaterModeWakeFromWakeGesture, + WAKE_REASON_GESTURE, + "GESTURE"); + } + + private boolean wakeUp( + long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason, String details) { + final boolean isTheaterModeEnabled = + Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1; + if (!wakeInTheaterMode && isTheaterModeEnabled) { + if (DEBUG) Slog.d(TAG, "Unable to wake up from " + details); + return false; + } + mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details); + return true; + } +} diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ca66a66057a3..6033220e260d 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -152,6 +152,7 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.WindowManagerPolicy; import com.android.server.utils.Slogf; +import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -795,6 +796,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + if (Flags.bundleClientTransactionFlag()) { + // mWmService.mResizingWindows is populated in #applySurfaceChangesTransaction() + handleResizingWindows(); + + // Called after #handleResizingWindows to include WindowStateResizeItem if any. + mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions(); + } + // Send any pending task-info changes that were queued-up during a layout deferment mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents(); @@ -838,12 +847,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - handleResizingWindows(); + if (!Flags.bundleClientTransactionFlag()) { + handleResizingWindows(); + } clearFrameChangingWindows(); - // Called after #handleResizingWindows to include WindowStateResizeItem if any. - mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions(); - if (mWmService.mDisplayFrozen) { ProtoLog.v(WM_DEBUG_ORIENTATION, "With display frozen, orientationChangeComplete=%b", diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index dda33f3f501b..502912a98816 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5748,7 +5748,6 @@ public class WindowManagerService extends IWindowManager.Stub case INSETS_CHANGED: { synchronized (mGlobalLock) { if (mWindowsInsetsChanged > 0) { - mWindowsInsetsChanged = 0; // We need to update resizing windows and dispatch the new insets state // to them. mWindowPlacerLocked.performSurfacePlacement(); @@ -6848,6 +6847,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(defaultDisplayContent.getLastOrientation()); pw.print(" mWaitingForConfig="); pw.println(defaultDisplayContent.mWaitingForConfig); + pw.print(" mWindowsInsetsChanged="); pw.println(mWindowsInsetsChanged); mRotationWatcherController.dump(pw); pw.print(" Animation settings: disabled="); pw.print(mAnimationsDisabled); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 58ade1bf5c1e..949025c45287 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1443,16 +1443,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP this, mWindowFrames.getInsetsChangedInfo(), configChanged, didFrameInsetsChange); - if (insetsChanged) { - mWindowFrames.setInsetsChanged(false); - if (mWmService.mWindowsInsetsChanged > 0) { - mWmService.mWindowsInsetsChanged--; - } - if (mWmService.mWindowsInsetsChanged == 0) { - mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); - } - } - + consumeInsetsChange(); onResizeHandled(); mWmService.makeWindowFreezingScreenIfNeededLocked(this); @@ -2349,6 +2340,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens( getWindowToken()); + + consumeInsetsChange(); } @Override @@ -3722,6 +3715,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mClient instanceof IWindow.Stub; } + private void consumeInsetsChange() { + if (mWindowFrames.hasInsetsChanged()) { + mWindowFrames.setInsetsChanged(false); + mWmService.mWindowsInsetsChanged--; + if (mWmService.mWindowsInsetsChanged == 0) { + mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); + } + } + } + /** * Called when the insets state changed. */ @@ -3729,10 +3732,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this); if (!mWindowFrames.hasInsetsChanged()) { mWindowFrames.setInsetsChanged(true); + mWmService.mWindowsInsetsChanged++; // If the new InsetsState won't be dispatched before releasing WM lock, the following // message will be executed. - mWmService.mWindowsInsetsChanged++; mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED); } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 4a2e1cba5cce..686b2a813bd3 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -65,7 +65,6 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.content.PackageMonitor; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; import com.android.server.infra.AbstractMasterSystemService; @@ -1204,6 +1203,9 @@ public final class CredentialManagerService // If the app being removed matches any of the package names from // this list then don't add it in the output. Set<String> providers = new HashSet<>(); + if (rawProviders == null || packageName == null) { + return providers; + } for (String rawComponentName : rawProviders.split(":")) { if (TextUtils.isEmpty(rawComponentName) || rawComponentName.equals("null")) { diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java index 6f9b6faa0bb0..8f5d1253406e 100644 --- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java +++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java @@ -62,7 +62,7 @@ import java.util.List; import java.util.Set; @RunWith(JUnit4.class) -public class AudioPoliciesDeviceRouteControllerTest { +public class AudioManagerRouteControllerTest { private static final String FAKE_ROUTE_NAME = "fake name"; private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER = @@ -89,7 +89,7 @@ public class AudioPoliciesDeviceRouteControllerTest { private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos; @Mock private AudioManager mMockAudioManager; @Mock private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; - private AudioPoliciesDeviceRouteController mControllerUnderTest; + private AudioManagerRouteController mControllerUnderTest; private AudioDeviceCallback mAudioDeviceCallback; private AudioProductStrategy mMediaAudioProductStrategy; @@ -116,7 +116,7 @@ public class AudioPoliciesDeviceRouteControllerTest { BluetoothAdapter btAdapter = realContext.getSystemService(BluetoothManager.class).getAdapter(); mControllerUnderTest = - new AudioPoliciesDeviceRouteController( + new AudioManagerRouteController( mockContext, mMockAudioManager, Looper.getMainLooper(), diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java index d2e83e9b0708..9eeb4f3f218f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java @@ -271,6 +271,7 @@ public class AuthenticationStatsCollectorTest { .thenReturn(true); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false); when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(null); mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */); diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java index fd1abff8610b..d850c73ebc26 100644 --- a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java @@ -53,6 +53,12 @@ public final class CredentialManagerServiceTest { } @Test + public void getStoredProviders_nullValue_success() { + Set<String> providers = CredentialManagerService.getStoredProviders(null, null); + assertThat(providers.size()).isEqualTo(0); + } + + @Test public void getStoredProviders_success() { Set<String> providers = CredentialManagerService.getStoredProviders( diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java deleted file mode 100644 index 06f117bdbd65..000000000000 --- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.media; - -import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER; - -import android.content.Context; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; - -import androidx.test.platform.app.InstrumentationRegistry; - -import com.google.common.truth.Truth; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class BluetoothRouteControllerTest { - - private final BluetoothRouteController.BluetoothRoutesUpdatedListener - mBluetoothRoutesUpdatedListener = - () -> { - // Empty on purpose. - }; - - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - private Context mContext; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getInstrumentation().getContext(); - } - - @Test - @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) - public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() { - BluetoothRouteController deviceRouteController = - BluetoothRouteController.createInstance(mContext, mBluetoothRoutesUpdatedListener); - - Truth.assertThat(deviceRouteController).isInstanceOf(LegacyBluetoothRouteController.class); - } - - @Test - @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) - public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() { - BluetoothRouteController deviceRouteController = - BluetoothRouteController.createInstance(mContext, mBluetoothRoutesUpdatedListener); - - Truth.assertThat(deviceRouteController) - .isInstanceOf(AudioPoliciesBluetoothRouteController.class); - } -} diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java index 0961b7d97177..eb789615b978 100644 --- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java @@ -70,7 +70,6 @@ public class DeviceRouteControllerTest { DeviceRouteController.createInstance( mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener); - Truth.assertThat(deviceRouteController) - .isInstanceOf(AudioPoliciesDeviceRouteController.class); + Truth.assertThat(deviceRouteController).isInstanceOf(AudioManagerRouteController.class); } } 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 3ab7496eff84..e1b1bff62031 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16,11 +16,14 @@ package com.android.server.notification; +import static android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS; +import static android.Manifest.permission.STATUS_BAR_SERVICE; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE; import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS; import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP; import static android.app.Notification.EXTRA_PICTURE; import static android.app.Notification.EXTRA_PICTURE_ICON; @@ -60,6 +63,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BA import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; +import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; +import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.PackageManager.FEATURE_TELECOM; import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -118,6 +123,7 @@ import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Matchers.anyBoolean; @@ -547,6 +553,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager); mContext.addMockSystemService(NotificationManager.class, mMockNm); + doNothing().when(mContext).sendBroadcast(any(), anyString()); doNothing().when(mContext).sendBroadcastAsUser(any(), any()); doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); @@ -909,7 +916,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } private ApplicationInfo getApplicationInfo(String pkg, int uid) { final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = pkg; applicationInfo.uid = uid; + applicationInfo.sourceDir = mContext.getApplicationInfo().sourceDir; switch (pkg) { case PKG_N_MR1: applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1; @@ -5535,15 +5544,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testBumpFGImportance_channelChangePreOApp() throws Exception { - String preOPkg = PKG_N_MR1; - final ApplicationInfo legacy = new ApplicationInfo(); - legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1; - when(mPackageManagerClient.getApplicationInfoAsUser(eq(preOPkg), anyInt(), anyInt())) - .thenReturn(legacy); - when(mPackageManagerClient.getPackageUidAsUser(eq(preOPkg), anyInt())) - .thenReturn(Binder.getCallingUid()); - getContext().setMockPackageManager(mPackageManagerClient); - Notification.Builder nb = new Notification.Builder(mContext, NotificationChannel.DEFAULT_CHANNEL_ID) .setContentTitle("foo") @@ -5551,7 +5551,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setFlag(FLAG_FOREGROUND_SERVICE, true) .setPriority(Notification.PRIORITY_MIN); - StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, + StatusBarNotification sbn = new StatusBarNotification(PKG_N_MR1, PKG_N_MR1, 9, "testBumpFGImportance_channelChangePreOApp", Binder.getCallingUid(), 0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0); @@ -5571,11 +5571,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setFlag(FLAG_FOREGROUND_SERVICE, true) .setPriority(Notification.PRIORITY_MIN); - sbn = new StatusBarNotification(preOPkg, preOPkg, 9, + sbn = new StatusBarNotification(PKG_N_MR1, PKG_N_MR1, 9, "testBumpFGImportance_channelChangePreOApp", Binder.getCallingUid(), 0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0); - mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, + mBinderService.enqueueNotificationWithTag(PKG_N_MR1, PKG_N_MR1, "testBumpFGImportance_channelChangePreOApp", sbn.getId(), sbn.getNotification(), sbn.getUserId()); waitForIdle(); @@ -5583,7 +5583,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.getNotificationRecord(sbn.getKey()).getImportance()); NotificationChannel defaultChannel = mBinderService.getNotificationChannel( - preOPkg, mContext.getUserId(), preOPkg, NotificationChannel.DEFAULT_CHANNEL_ID); + PKG_N_MR1, mContext.getUserId(), PKG_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID); assertEquals(IMPORTANCE_LOW, defaultChannel.getImportance()); } @@ -11266,6 +11266,40 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testMigrateNotificationFilter_invalidPackage() throws Exception { + int[] userIds = new int[] {mUserId, 1000}; + when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds); + List<String> disallowedApps = ImmutableList.of("apples", "bananas", "cherries"); + for (int userId : userIds) { + when(mPackageManager.getPackageUid("apples", 0, userId)).thenThrow( + new RemoteException("")); + when(mPackageManager.getPackageUid("bananas", 0, userId)).thenReturn(9000); + when(mPackageManager.getPackageUid("cherries", 0, userId)).thenReturn(9001); + } + + when(mListeners.getNotificationListenerFilter(any())).thenReturn( + new NotificationListenerFilter()); + + mBinderService.migrateNotificationFilter(null, + FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ONGOING, + disallowedApps); + + ArgumentCaptor<NotificationListenerFilter> captor = + ArgumentCaptor.forClass(NotificationListenerFilter.class); + verify(mListeners).setNotificationListenerFilter(any(), captor.capture()); + + assertEquals(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ONGOING, + captor.getValue().getTypes()); + // valid values stay + assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("bananas", 9000))); + assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("cherries", 9001))); + // don't store invalid values + for (VersionedPackage vp : captor.getValue().getDisallowedPackages()) { + assertNotEquals("apples", vp.getPackageName()); + } + } + + @Test public void testMigrateNotificationFilter_noPreexistingFilter() throws Exception { int[] userIds = new int[] {mUserId}; when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds); @@ -14059,6 +14093,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { any(), any()); } + @Test + @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS) + public void testSetPrivateNotificationsAllowed() throws Exception { + when(mContext.checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) + .thenReturn(PERMISSION_GRANTED); + mBinderService.setPrivateNotificationsAllowed(false); + Intent expected = new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED) + .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false); + ArgumentCaptor<Intent> actual = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendBroadcast(actual.capture(), eq(STATUS_BAR_SERVICE)); + + assertEquals(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED, actual.getValue().getAction()); + assertFalse(actual.getValue().getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true)); + assertFalse(mBinderService.getPrivateNotificationsAllowed()); + } + private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java index 1fcee0658afc..5b35e345e46b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java @@ -118,10 +118,13 @@ public class ZenModeEventLoggerFake extends ZenModeEventLogger { public DNDPolicyProto getPolicyProto(int i) throws IllegalArgumentException { checkInRange(i); byte[] policyBytes = mChanges.get(i).getDNDPolicyProto(); + if (policyBytes == null) { + return null; + } try { return DNDPolicyProto.parseFrom(policyBytes); } catch (InvalidProtocolBufferException e) { - return null; // couldn't turn it into proto + throw new RuntimeException("Couldn't parse DNDPolicyProto!", e); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 44f0894f76d7..25c0cd9fae25 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -2429,7 +2429,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(0, mZenModeEventLogger.getNumRulesActive(1)); assertFalse(mZenModeEventLogger.getIsUserAction(1)); assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1)); - checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1)); + if (Flags.modesApi()) { + assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull(); + } else { + checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1)); + } } @Test @@ -2511,7 +2515,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(0, mZenModeEventLogger.getNumRulesActive(1)); assertTrue(mZenModeEventLogger.getIsUserAction(1)); assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1)); - checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1)); + if (Flags.modesApi()) { + assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull(); + } else { + checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1)); + } // When the system rule is enabled, this counts as an automatic action that comes from the // system and turns on DND @@ -3016,6 +3024,48 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testZenModeEventLog_ruleWithInterruptionFilterAll_notLoggedAsDndChange() { + mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); + setupZenConfig(); + + // An app adds an automatic zen rule + AutomaticZenRule zenRule = new AutomaticZenRule("name", + null, + new ComponentName(CUSTOM_PKG_NAME, "cls"), + Uri.parse("condition"), + null, + NotificationManager.INTERRUPTION_FILTER_ALL, true); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID); + + // Event 1: App activates the rule automatically. + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE), + UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); + + // Event 2: App deactivates the rule automatically. + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE), + UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); + + // In total, this represents 2 events. + assertEquals(2, mZenModeEventLogger.numLoggedChanges()); + + // However, they are not DND_TURNED_ON/_OFF (no notification filtering is taking place). + // Also, no consolidated ZenPolicy is logged (because of the same reason). + assertThat(mZenModeEventLogger.getEventId(0)).isEqualTo( + ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId()); + assertThat(mZenModeEventLogger.getNumRulesActive(0)).isEqualTo(1); + assertThat(mZenModeEventLogger.getPolicyProto(0)).isNull(); + + assertThat(mZenModeEventLogger.getEventId(1)).isEqualTo( + ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId()); + assertThat(mZenModeEventLogger.getNumRulesActive(1)).isEqualTo(0); + assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull(); + } + + @Test public void testUpdateConsolidatedPolicy_defaultRulesOnly() { setupZenConfig(); @@ -3203,6 +3253,52 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll() { + setupZenConfig(); + + // Rules with INTERRUPTION_FILTER_ALL are skipped when calculating consolidated policy. + // Note: rules with filter != PRIORITY should not have a custom policy. However, as of V + // this is only validated on rule addition, but not on rule update. :/ + + // Rule 1: PRIORITY, custom policy but not very strict (in fact, less strict than default). + AutomaticZenRule zenRuleWithPriority = new AutomaticZenRule("Priority", + null, + new ComponentName(CUSTOM_PKG_NAME, "cls"), + Uri.parse("priority"), + new ZenPolicy.Builder().allowMedia(true).build(), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + zenRuleWithPriority, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID); + mZenModeHelper.setAutomaticZenRuleState(rule1Id, + new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); + + // Rule 2: ALL, but somehow with a super strict ZenPolicy. + AutomaticZenRule zenRuleWithAll = new AutomaticZenRule("All", + null, + new ComponentName(CUSTOM_PKG_NAME, "cls"), + Uri.parse("priority"), + new ZenPolicy.Builder().disallowAllSounds().build(), + NotificationManager.INTERRUPTION_FILTER_ALL, true); + String rule2Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + zenRuleWithAll, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID); + mZenModeHelper.setAutomaticZenRuleState(rule2Id, + new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); + + // Consolidated Policy should be default + rule1. + assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // priority rule + assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()).isTrue(); // default + } + + @Test public void zenRuleToAutomaticZenRule_allFields() { mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java index bfc17713b6a8..336bfdd0fb14 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java @@ -35,6 +35,7 @@ import android.util.Slog; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.Keep; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType; @@ -178,6 +179,7 @@ public class BroadcastResponseStatsLogger { } } + @Keep public static final class BroadcastEvent implements Data { public int sourceUid; public int targetUserId; @@ -198,6 +200,7 @@ public class BroadcastResponseStatsLogger { } } + @Keep public static final class NotificationEvent implements Data { public int type; public String packageName; |