Merge "[flexiglass] Separates smartspace view from view-model" into main
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index 4352c8a..ea10690 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.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 @@
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 385fd50..14195c4 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -241,6 +241,23 @@
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 fb0edb9..d11c6c5 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -36,3 +36,10 @@
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 3ce094e..cbb0ae7 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -23,6 +23,7 @@
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 @@
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 d2ef65a..1190bf6 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -24,12 +24,14 @@
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 @@
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 961da19..1353d16 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -22,6 +22,7 @@
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 @@
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 ee48e43..5dd4eb7 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -41,6 +41,7 @@
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 @@
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 @@
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 @@
}
}
- // 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 7d3eb87..193b03c 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -22,9 +22,12 @@
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 @@
@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 @@
// 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 @@
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 @@
+ ", 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/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 83d237d..d646146 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -91,3 +91,10 @@
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 78248d9..e1965ef 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.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 @@
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 f7b9aa2..079991a 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -76,7 +76,7 @@
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 @@
});
}
- 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 147c15b..dd09157 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1353,6 +1353,9 @@
});
}
+ // 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 @@
}
}
+ 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 ec99459..442ea66 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10760,11 +10760,11 @@
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 fcdad66..32afe06 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -111,6 +111,7 @@
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.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 @@
}
/** 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 @@
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 @@
}
}
- 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
}
@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,
- forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+ 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
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 44b4353..70c899f 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -17,10 +17,16 @@
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 @@
}
@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 @@
@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 internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
- getMainCaptureSession().notifyViewAppeared(mId, node);
+ void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+ @NonNull ContentCaptureContext clientContext) {
+ getMainCaptureSession()
+ .internalNotifyChildSessionStarted(parentSessionId, childSessionId, clientContext);
}
@Override
- void internalNotifyViewDisappeared(@NonNull AutofillId id) {
- getMainCaptureSession().notifyViewDisappeared(mId, id);
+ void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+ getMainCaptureSession().internalNotifyChildSessionFinished(parentSessionId, childSessionId);
}
@Override
- void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
- getMainCaptureSession().notifyViewTextChanged(mId, id, text);
+ void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+ getMainCaptureSession().internalNotifyContextUpdated(sessionId, context);
}
@Override
- void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) {
- getMainCaptureSession().notifyViewInsetsChanged(mId, viewInsets);
+ void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
+ getMainCaptureSession().internalNotifyViewAppeared(sessionId, node);
}
@Override
- public void internalNotifyViewTreeEvent(boolean started) {
- getMainCaptureSession().notifyViewTreeEvent(mId, started);
+ void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
+ getMainCaptureSession().internalNotifyViewDisappeared(sessionId, id);
+ }
+
+ @Override
+ void internalNotifyViewTextChanged(
+ int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+ getMainCaptureSession().internalNotifyViewTextChanged(sessionId, id, text);
+ }
+
+ @Override
+ void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
+ getMainCaptureSession().internalNotifyViewInsetsChanged(mId, viewInsets);
+ }
+
+ @Override
+ 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 a829747..bcef37f 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -499,10 +499,14 @@
@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 @@
*/
@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 @@
@NonNull
@GuardedBy("mLock")
- private Handler prepareContentCaptureHandler() {
- if (mHandler == null) {
- if (runOnBackgroundThreadEnabled()) {
- mHandler = BackgroundThread.getHandler();
- } else {
- mHandler = Handler.createAsync(Looper.getMainLooper());
- }
+ private ContentCaptureSession prepareMainSession() {
+ if (runOnBackgroundThreadEnabled()) {
+ return new MainContentCaptureSessionV2(
+ mContext,
+ this,
+ prepareUiHandler(),
+ prepareContentCaptureHandler(),
+ mService
+ );
+ } else {
+ return new MainContentCaptureSession(mContext, this, prepareUiHandler(), mService);
}
- return mHandler;
+ }
+
+ @NonNull
+ @GuardedBy("mLock")
+ private Handler prepareContentCaptureHandler() {
+ if (mContentCaptureHandler == null) {
+ mContentCaptureHandler = BackgroundThread.getHandler();
+ }
+ return mContentCaptureHandler;
+ }
+
+ @NonNull
+ @GuardedBy("mLock")
+ private Handler prepareUiHandler() {
+ if (mUiHandler == null) {
+ mUiHandler = Handler.createAsync(Looper.getMainLooper());
+ }
+ return mUiHandler;
}
/** @hide */
@@ -726,7 +750,7 @@
public boolean isContentCaptureEnabled() {
if (mOptions.lite) return false;
- final MainContentCaptureSession mainSession;
+ final ContentCaptureSession mainSession;
synchronized (mLock) {
mainSession = mMainSession;
}
@@ -777,7 +801,7 @@
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 @@
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 bb815c0..0ca36ba2 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -27,9 +27,13 @@
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 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 @@
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 @@
/** @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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
if (!isContentCaptureEnabled()) return;
- internalNotifyViewInsetsChanged(viewInsets);
+ internalNotifyViewInsetsChanged(mId, viewInsets);
}
- abstract void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets);
+ abstract void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets);
/** @hide */
- public abstract void internalNotifyViewTreeEvent(boolean started);
+ public void notifyViewTreeEvent(boolean started) {
+ internalNotifyViewTreeEvent(mId, started);
+ }
+
+ /** @hide */
+ abstract void internalNotifyViewTreeEvent(int sessionId, boolean started);
/**
* Notifies the Content Capture Service that a session has resumed.
@@ -543,6 +578,21 @@
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 19ba316..a90c94e 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.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.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 @@
*
* @hide
*/
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class MainContentCaptureSession extends ContentCaptureSession {
private static final String TAG = MainContentCaptureSession.class.getSimpleName();
@@ -97,18 +97,6 @@
*/
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 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 @@
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 @@
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 @@
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 @@
*/
@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 @@
}
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 @@
}
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 @@
}
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 @@
+ 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 @@
@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 @@
@Override
public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
- notifyContextUpdated(mId, context);
+ internalNotifyContextUpdated(mId, context);
}
/**
@@ -711,7 +672,7 @@
*/
@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 @@
/** 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 @@
}
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 @@
/** @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 @@
}
@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 @@
.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) {
+ @Override
+ boolean isContentCaptureEnabled() {
+ return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
+ }
+
+ @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(childSessionId, TYPE_SESSION_STARTED)
+ .setParentSessionId(parentSessionId)
+ .setClientContext(clientContext);
+ mHandler.post(() -> sendEvent(event, FORCE_FLUSH));
+ }
+
+ @Override
+ void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+ final ContentCaptureEvent event =
+ new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+ .setParentSessionId(parentSessionId);
+ mHandler.post(() -> sendEvent(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);
+ mHandler.post(() -> sendEvent(event, FORCE_FLUSH));
}
- /** public because is also used by ViewRootImpl */
+ @Override
public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
final ContentCaptureEvent event =
new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
.setBounds(bounds);
- enqueueEvent(event);
+ mHandler.post(() -> sendEvent(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 (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));
- }
- }
-
- /** 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 @@
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 @@
* 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 @@
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 0000000..bf1d31c8
--- /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 f05d9cb..983658a 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 @@
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 c6209dd..232a36f 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 785a8a1..0000000
--- 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 0000000..b5e8203
--- /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 d9f5523..0000000
--- 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 ecd75a8..0000000
--- 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 a801a76..0000000
--- 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 cf9935f..0000000
--- 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 4d45daf..0000000
--- 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 35ddfdb..e52aa1b 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 @@
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 @@
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 @@
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 23b9b9b..4a4c693 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 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.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.ArrayList;
import java.util.Map;
/**
@@ -195,6 +203,22 @@
}
@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 @@
}
@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 @@
}
@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 0000000..f0f3a96
--- /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 15ee4e1..65597de 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.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.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.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 @@
/** 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 @@
}
@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 void setActivityStackAttributesCalculator(
@NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
calculator) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return;
+ }
synchronized (mLock) {
mActivityStackAttributesCalculator = calculator;
}
@@ -340,6 +358,9 @@
@Override
public void clearActivityStackAttributesCalculator() {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return;
+ }
synchronized (mLock) {
mActivityStackAttributesCalculator = null;
}
@@ -351,7 +372,7 @@
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 @@
/**
* 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 @@
/**
* 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 @@
@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 @@
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 @@
}
}
+ @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 @@
/**
* 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 @@
@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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
* 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 @@
* 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 @@
}
}
- /** 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 @@
/**
* 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 @@
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 @@
/**
* 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 @@
@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 @@
*/
@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 @@
}
}
- /** 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 @@
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 @@
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 @@
* 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 @@
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 @@
*/
@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 @@
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 @@
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 @@
@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 @@
* 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 @@
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 @@
@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 @@
* 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 @@
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 @@
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 @@
/**
* 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 @@
@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 @@
}
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 @@
}
/**
+ * 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 @@
@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 @@
* 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 @@
// 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 @@
&& 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 @@
}
}
- /** 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 @@
&& 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 acfd8e4..543570c 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 @@
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 @@
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 @@
} 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 @@
// 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 @@
&& 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 @@
// 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 @@
* 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 @@
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 @@
}
@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 028e75f..64ad4fa 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 @@
* 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 @@
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 afd554b..810bded 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 @@
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 @@
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 @@
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 678bdef..5ef6a52 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 @@
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 @@
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 @@
.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 bab4e91..b60943a 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 @@
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 @@
// 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 7b77235..a5995a3 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 @@
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 @@
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 @@
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 f75ccea..d8650e1 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 8f5f1f6..4fbe9ee 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 3b15632..ce1004c 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 @@
@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 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 @@
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 @@
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 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();
+ final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+ final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
- argument.capture());
- assertEquals(argument.getValue().size(), 1);
+ try {
+ mLcc.addMediaCodec(mediaCodec1);
+ 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()));
+ mLcc.addMediaCodec(mediaCodec2);
+ mLcc.setAudioTrack(null);
+ verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+ } finally {
+ mediaCodec1.release();
+ mediaCodec2.release();
+ }
}
@Test
@@ -186,24 +219,35 @@
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();
+ final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+ final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+ try {
+ mLcc.addMediaCodec(mediaCodec1);
+ mLcc.setAudioTrack(track);
+ verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
+ mLcc.addMediaCodec(mediaCodec2);
+ verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
+ } finally {
+ mediaCodec1.release();
+ mediaCodec2.release();
+ }
}
@Test
@@ -212,25 +256,36 @@
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();
+ final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+ final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+ try {
+ mLcc.addMediaCodec(mediaCodec1);
+ mLcc.setAudioTrack(track);
+ verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
- assertThrows(IllegalArgumentException.class,
- () -> mLcc.removeMediaCodec(createAndConfigureMediaCodec()));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLcc.removeMediaCodec(mediaCodec2));
+ } finally {
+ mediaCodec1.release();
+ mediaCodec2.release();
+ }
}
private static AudioTrack createAudioTrack() {
@@ -250,19 +305,21 @@
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);
- 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;
+ 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 9f2a9ac..9605108 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/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 41d12dc..d9286b3 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -212,6 +212,13 @@
}
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
similarity index 100%
rename from packages/SystemUI/animation/core/Android.bp
rename to packages/SystemUI/animation/Android.bp
diff --git a/packages/SystemUI/animation/core/AndroidManifest.xml b/packages/SystemUI/animation/AndroidManifest.xml
similarity index 100%
rename from packages/SystemUI/animation/core/AndroidManifest.xml
rename to packages/SystemUI/animation/AndroidManifest.xml
diff --git a/packages/SystemUI/animation/core/build.gradle b/packages/SystemUI/animation/build.gradle
similarity index 81%
rename from packages/SystemUI/animation/core/build.gradle
rename to packages/SystemUI/animation/build.gradle
index 12637f4..939455f 100644
--- a/packages/SystemUI/animation/core/build.gradle
+++ b/packages/SystemUI/animation/build.gradle
@@ -5,8 +5,8 @@
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
similarity index 100%
rename from packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/res/values/ids.xml
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/res/values/styles.xml
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt
rename to 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
similarity index 93%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index dc54240..8dc7495 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 @@
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 @@
*
* 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 @@
/**
* 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 @@
}
/**
- * 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 @@
* 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 @@
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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
rename to 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
similarity index 92%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index a9c53d1..00d9056 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 @@
*
* @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 @@
// 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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
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 @@
// - 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 @@
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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt
rename to 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
similarity index 100%
rename from packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt
rename to 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 3fbcf6d..1519021 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.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 @@
.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 74c9225..e2bdc49 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.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 @@
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 @@
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 @@
@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 d41c249..e893169 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.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 @@
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 f170135..1fc2843 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.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 @@
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 84d73543..cbcca55 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.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 @@
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 7b1f302..45a60199 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.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 @@
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/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index 1ee58de..5e76801 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.util.settings.SecureSettings
import java.io.PrintWriter
import javax.inject.Inject
+import dagger.Lazy
/**
* Handles active unlock settings changes.
@@ -51,6 +52,7 @@
private val secureSettings: SecureSettings,
private val contentResolver: ContentResolver,
private val selectedUserInteractor: SelectedUserInteractor,
+ private val keyguardUpdateMonitor: Lazy<KeyguardUpdateMonitor>,
dumpManager: DumpManager
) : Dumpable {
@@ -96,7 +98,6 @@
UNDER_DISPLAY_FINGERPRINT(3),
}
- var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
private var requestActiveUnlockOnWakeup = false
private var requestActiveUnlockOnUnlockIntent = false
private var requestActiveUnlockOnBioFail = false
@@ -316,7 +317,7 @@
return false
}
- keyguardUpdateMonitor?.let {
+ keyguardUpdateMonitor.get().let {
val anyFaceEnrolled = it.isFaceEnabledAndEnrolled
val anyFingerprintEnrolled = it.isUnlockWithFingerprintPossible(
selectedUserInteractor.getSelectedUserId())
@@ -369,13 +370,13 @@
}")
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 714fe64..66f965a 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.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 @@
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 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 cce2018..5e35e77 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.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 @@
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 9c61a8a..f3cd01f 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.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 @@
* 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 @@
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 @@
@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 @@
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 @@
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context);
mBackgroundExecutor = backgroundExecutor;
+ mMainExecutor = mainExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mInteractionJankMonitor = interactionJankMonitor;
mLatencyTracker = latencyTracker;
@@ -2126,7 +2132,7 @@
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 @@
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 @@
}
};
+ 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 @@
mHandler, UserHandle.ALL);
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
- mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
mTrustManager.registerTrustListener(this);
@@ -2318,8 +2342,8 @@
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 @@
@Override
public void onAllAuthenticatorsRegistered(
@BiometricAuthenticator.Modality int modality) {
- mainExecutor.execute(
+ mMainExecutor.execute(
() -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE));
}
@@ -2335,7 +2359,7 @@
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 @@
}
});
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 @@
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/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index f612f9a..b587ed8 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.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.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 @@
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 @@
}
fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
- if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+ if (!Flags.revampedBouncerMessages()) return
val callback =
object : CountDownTimerCallback {
@@ -250,7 +248,7 @@
}
fun onPrimaryAuthIncorrectAttempt() {
- if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+ if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
@@ -258,21 +256,21 @@
}
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 @@
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 5a59012..b8e6ad6 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 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 @@
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 5dcd661..cc387e9 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.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 @@
messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
bouncerMessageInteractor: BouncerMessageInteractor,
bouncerLogger: BouncerLogger,
- featureFlags: FeatureFlags,
selectedUserInteractor: SelectedUserInteractor,
) {
// Builds the KeyguardSecurityContainerController from bouncer view group.
@@ -141,8 +139,7 @@
it.bindMessageView(
bouncerMessageInteractor,
messageAreaControllerFactory,
- bouncerLogger,
- featureFlags
+ bouncerLogger
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 1b35005..bb0c273 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 @@
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/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 3925dd1..13e3835 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.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.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 @@
StartKeyguardTransitionModule.class,
ResourceTrimmerModule.class,
})
-public class KeyguardModule {
+public interface KeyguardModule {
/**
* Provides our instance of KeyguardViewMediator which is considered optional.
*/
@@ -207,13 +211,19 @@
/** */
@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/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 3cf468f..8162d58 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 @@
messageAreaControllerFactory,
bouncerMessageInteractor,
bouncerLogger,
- featureFlagsClassic,
selectedUserInteractor);
if (DeviceEntryUdfpsRefactor.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 633510d..c9df317 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_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.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 @@
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 @@
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 @@
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 @@
mLockscreenSettingsObserver.onChange(
false, mLockScreenUris, 0, UserHandle.of(userId));
updateDpcSettings(userId);
+
+ if (keyguardPrivateNotifications()) {
+ updateGlobalKeyguardSettings();
+ }
}
public boolean shouldShowLockscreenNotifications() {
@@ -477,8 +517,12 @@
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 @@
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 @@
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 @@
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 @@
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 3f76d30..8858d13 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.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import dagger.Lazy
@SmallTest
class ActiveUnlockConfigTest : SysuiTestCase() {
@@ -60,6 +61,7 @@
@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 @@
MockitoAnnotations.initMocks(this)
whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUser)
+ whenever(lazyKeyguardUpdateMonitor.get()).thenReturn(keyguardUpdateMonitor)
secureSettings = FakeSettings()
activeUnlockConfig =
ActiveUnlockConfig(
@@ -79,6 +82,7 @@
secureSettings,
contentResolver,
selectedUserInteractor,
+ lazyKeyguardUpdateMonitor,
dumpManager
)
}
@@ -260,7 +264,6 @@
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 @@
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 7f20d9a..51ceda3 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.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 @@
.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 @@
@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 1ab634c..d03a898 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 @@
Optional.of(mInteractiveToAuthProvider),
mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
+ start();
}
public boolean hasSimStateJustChanged() {
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 aa0d7b6..45a426e 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.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.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 @@
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 @@
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/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 86d8d54..1e1e344 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.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 @@
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 @@
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 d9ff892..dd4ac9d 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.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 @@
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 @@
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 42c7375..0c6f456 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 @@
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 @@
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
// DevicePolicy allows notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index 3a90a95..73ed97f 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.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 @@
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 96b1650..02f4485 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 @@
// 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 @@
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/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
similarity index 98%
rename from services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
rename to services/core/java/com/android/server/media/AudioManagerRouteController.java
index 0eb9166..5c9e61a 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 @@
* <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 @@
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 8b62cc9..dff0adf 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 @@
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 1b49093..b3e5b9e 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 85731651..4d19ead 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.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 @@
* 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 ff415c1..4d7d8cf 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_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.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 @@
.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 @@
/**
* 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();
@@ -5569,7 +5571,7 @@
private void enforceSystemOrSystemUI(String message) {
if (isCallerSystemOrPhone()) return;
- getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ getContext().enforceCallingPermission(STATUS_BAR_SERVICE,
message);
}
@@ -5578,7 +5580,7 @@
checkCallerIsSystemOrSameApp(pkg);
} catch (SecurityException e) {
getContext().enforceCallingPermission(
- android.Manifest.permission.STATUS_BAR_SERVICE,
+ STATUS_BAR_SERVICE,
message);
}
}
@@ -6183,13 +6185,20 @@
@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 +6206,7 @@
@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 +8377,8 @@
boolean posted = false;
try {
posted = postNotification();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error posting", e);
} finally {
if (!posted) {
mTracker.cancel();
@@ -9557,12 +9567,16 @@
}
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 +9656,24 @@
}
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 +9707,9 @@
}
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 +10614,7 @@
if (isCallerSystemOrPhone()) {
return true;
}
- return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ return getContext().checkCallingPermission(STATUS_BAR_SERVICE)
== PERMISSION_GRANTED;
}
@@ -10628,7 +10653,7 @@
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 df570a0..0145577 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_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 @@
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 @@
}
}
+ 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 @@
* 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 @@
// 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 @@
/**
* 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 0a46901..b7a2038 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 @@
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 8e79922a..49db7fc 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 @@
# 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 5b13d3fe..edce3ec 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 @@
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 @@
// 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 @@
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 @@
|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) {
if (!interactive) {
- wakeUpFromPowerKey(event.getDownTime());
+ wakeUpFromWakeKey(event);
}
} else {
// handled by another power key policy.
@@ -1309,7 +1301,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
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 @@
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 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 @@
// 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,37 +5525,22 @@
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) {
- if (wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey,
- PowerManager.WAKE_REASON_WAKE_KEY, "android.policy:KEY")) {
- // 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));
- }
- }
+ wakeUpFromWakeKey(event.getEventTime(), event.getKeyCode());
}
- private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason,
- String details) {
- final boolean theaterModeEnabled = isTheaterModeEnabled();
- if (!wakeInTheaterMode && theaterModeEnabled) {
- return false;
+ 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() && keyCanLaunchHome) {
+ startDockOrHome(
+ DEFAULT_DISPLAY,
+ /*fromHomeKey*/ keyCode == KEYCODE_HOME,
+ /*wakenFromDreams*/ true,
+ "Wake from " + KeyEvent. keyCodeToString(keyCode));
+ }
}
-
- mPowerManager.wakeUp(wakeTime, reason, details);
- return true;
}
private void finishKeyguardDrawn() {
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 0000000..392d0d4
--- /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 ca66a66..6033220 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.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 @@
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 @@
}
}
- 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 dda33f3..502912a 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 @@
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 @@
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 58ade1b..949025c 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 @@
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 @@
mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
getWindowToken());
+
+ consumeInsetsChange();
}
@Override
@@ -3722,6 +3715,16 @@
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 @@
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/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
similarity index 98%
rename from services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
rename to services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
index 6f9b6fa..8f5d125 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.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 @@
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 @@
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/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
deleted file mode 100644
index 06f117b..0000000
--- 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 0961b7d..eb78961 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 @@
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 3ab7496..0b459f6 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.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;
@@ -547,6 +552,7 @@
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 +915,9 @@
}
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 +5543,6 @@
@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 +5550,7 @@
.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 +5570,11 @@
.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 +5582,7 @@
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());
}
@@ -14059,6 +14058,22 @@
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 1fcee06..5b35e34 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 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 44f0894..25c0cd9 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 @@
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 @@
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 @@
}
@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 @@
}
@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 bfc1771..336bfdd 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.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 @@
}
}
+ @Keep
public static final class BroadcastEvent implements Data {
public int sourceUid;
public int targetUserId;
@@ -198,6 +200,7 @@
}
}
+ @Keep
public static final class NotificationEvent implements Data {
public int type;
public String packageName;