diff options
100 files changed, 1874 insertions, 985 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 41151c0dc647..1d39186de183 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -40,6 +40,7 @@ import static android.window.ConfigurationHelper.shouldUpdateResources; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; +import static com.android.window.flags.Flags.activityWindowInfoFlag; import android.annotation.NonNull; import android.annotation.Nullable; @@ -63,6 +64,7 @@ import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionListenerController; import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.PendingTransactionActions; @@ -606,6 +608,8 @@ public final class ActivityThread extends ClientTransactionHandler Configuration overrideConfig; @NonNull private ActivityWindowInfo mActivityWindowInfo; + @Nullable + private ActivityWindowInfo mLastReportedActivityWindowInfo; // Used for consolidating configs before sending on to Activity. private final Configuration tmpConfig = new Configuration(); @@ -4180,6 +4184,9 @@ public final class ActivityThread extends ClientTransactionHandler pendingActions.setRestoreInstanceState(true); pendingActions.setCallOnPostCreate(true); } + + // Trigger ActivityWindowInfo callback if first launch or change from relaunch. + handleActivityWindowInfoChanged(r); } else { // If there was an error, for any reason, tell the activity manager to stop us. ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED, @@ -4558,7 +4565,7 @@ public final class ActivityThread extends ClientTransactionHandler private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) { final ClientTransaction transaction = ClientTransaction.obtain(mAppThread); final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token, - r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags, + r.activity.isFinishing(), /* userLeaving */ true, /* dontReport */ false, /* autoEnteringPip */ false); transaction.addTransactionItem(pauseActivityItem); executeTransaction(transaction); @@ -5432,13 +5439,12 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving, - int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions, + boolean autoEnteringPip, PendingTransactionActions pendingActions, String reason) { if (userLeaving) { performUserLeavingActivity(r); } - r.activity.mConfigChangeFlags |= configChanges; if (autoEnteringPip) { // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}. @@ -5687,9 +5693,8 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public void handleStopActivity(ActivityClientRecord r, int configChanges, + public void handleStopActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) { - r.activity.mConfigChangeFlags |= configChanges; final StopInfo stopInfo = new StopInfo(); performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest, @@ -5859,11 +5864,10 @@ public final class ActivityThread extends ClientTransactionHandler /** Core implementation of activity destroy call. */ void performDestroyActivity(ActivityClientRecord r, boolean finishing, - int configChanges, boolean getNonConfigInstance, String reason) { + boolean getNonConfigInstance, String reason) { Class<? extends Activity> activityClass; if (localLOGV) Slog.v(TAG, "Performing finish of " + r); activityClass = r.activity.getClass(); - r.activity.mConfigChangeFlags |= configChanges; if (finishing) { r.activity.mFinished = true; } @@ -5928,9 +5932,9 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, + public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, boolean getNonConfigInstance, String reason) { - performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason); + performDestroyActivity(r, finishing, getNonConfigInstance, reason); cleanUpPendingRemoveWindows(r, finishing); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; @@ -6130,7 +6134,7 @@ public final class ActivityThread extends ClientTransactionHandler r.activity.mChangingConfigurations = true; - handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents, + handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents, pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo, "handleRelaunchActivity"); } @@ -6199,7 +6203,7 @@ public final class ActivityThread extends ClientTransactionHandler executeTransaction(transaction); } - private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, int configChanges, + private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, @Nullable List<ResultInfo> pendingResults, @Nullable List<ReferrerIntent> pendingIntents, @NonNull PendingTransactionActions pendingActions, boolean startsNotResumed, @@ -6215,7 +6219,7 @@ public final class ActivityThread extends ClientTransactionHandler callActivityOnStop(r, true /* saveState */, reason); } - handleDestroyActivity(r, false, configChanges, true, reason); + handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason); r.activity = null; r.window = null; @@ -6740,7 +6744,7 @@ public final class ActivityThread extends ClientTransactionHandler // Perform updates. r.overrideConfig = overrideConfig; r.mActivityWindowInfo = activityWindowInfo; - // TODO(b/287582673): notify on ActivityWindowInfo change + final ViewRootImpl viewRoot = r.activity.mDecor != null ? r.activity.mDecor.getViewRootImpl() : null; @@ -6763,6 +6767,22 @@ public final class ActivityThread extends ClientTransactionHandler viewRoot.updateConfiguration(displayId); } mSomeActivitiesChanged = true; + + // Trigger ActivityWindowInfo callback if changed. + handleActivityWindowInfoChanged(r); + } + + private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) { + if (!activityWindowInfoFlag()) { + return; + } + if (r.mActivityWindowInfo == null + || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) { + return; + } + r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo; + ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token, + r.mActivityWindowInfo); } final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) { diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index b5b3669c1d80..01153c9e7efc 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -114,11 +114,11 @@ public abstract class ClientTransactionHandler { /** Destroy the activity. */ public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing, - int configChanges, boolean getNonConfigInstance, String reason); + boolean getNonConfigInstance, String reason); /** Pause the activity. */ public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished, - boolean userLeaving, int configChanges, boolean autoEnteringPip, + boolean userLeaving, boolean autoEnteringPip, PendingTransactionActions pendingActions, String reason); /** @@ -146,14 +146,13 @@ public abstract class ClientTransactionHandler { /** * Stop the activity. * @param r Target activity record. - * @param configChanges Activity configuration changes. * @param pendingActions Pending actions to be used on this or later stages of activity * transaction. * @param finalStateRequest Flag indicating if this call is handling final lifecycle state * request for a transaction. * @param reason Reason for performing this operation. */ - public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges, + public abstract void handleStopActivity(@NonNull ActivityClientRecord r, PendingTransactionActions pendingActions, boolean finalStateRequest, String reason); /** Report that activity was stopped to server. */ diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java index 1b19ecdd5931..095cfc5a7303 100644 --- a/core/java/android/app/LocalActivityManager.java +++ b/core/java/android/app/LocalActivityManager.java @@ -413,7 +413,7 @@ public class LocalActivityManager { if (localLOGV) Log.v(TAG, r.id + ": destroying"); final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); if (clientRecord != null) { - mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */, + mActivityThread.performDestroyActivity(clientRecord, finish, false /* getNonConfigInstance */, "LocalActivityManager::performDestroy"); } r.activity = null; @@ -684,7 +684,7 @@ public class LocalActivityManager { if (localLOGV) Log.v(TAG, r.id + ": no corresponding record"); continue; } - mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */, + mActivityThread.performDestroyActivity(clientRecord, finishing, false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy"); } mActivities.clear(); diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java index 1a8136e06c28..7383d07c82e9 100644 --- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -16,16 +16,24 @@ package android.app.servertransaction; +import static com.android.window.flags.Flags.activityWindowInfoFlag; import static com.android.window.flags.Flags.bundleClientTransactionFlag; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; +import android.app.Activity; import android.app.ActivityThread; import android.hardware.display.DisplayManagerGlobal; +import android.os.IBinder; +import android.util.ArraySet; +import android.window.ActivityWindowInfo; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.util.function.BiConsumer; + /** * Singleton controller to manage listeners to individual {@link ClientTransaction}. * @@ -35,8 +43,14 @@ public class ClientTransactionListenerController { private static ClientTransactionListenerController sController; + private final Object mLock = new Object(); private final DisplayManagerGlobal mDisplayManager; + /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */ + @GuardedBy("mLock") + private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>> + mActivityWindowInfoChangedListeners = new ArraySet<>(); + /** Gets the singleton controller. */ @NonNull public static ClientTransactionListenerController getInstance() { @@ -62,6 +76,57 @@ public class ClientTransactionListenerController { } /** + * Registers to listen on activity {@link ActivityWindowInfo} change. + * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and + * {@link ActivityWindowInfo}. + */ + public void registerActivityWindowInfoChangedListener( + @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) { + if (!activityWindowInfoFlag()) { + return; + } + synchronized (mLock) { + mActivityWindowInfoChangedListeners.add(listener); + } + } + + /** + * Unregisters the listener that was previously registered via + * {@link #registerActivityWindowInfoChangedListener(BiConsumer)} + */ + public void unregisterActivityWindowInfoChangedListener( + @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) { + if (!activityWindowInfoFlag()) { + return; + } + synchronized (mLock) { + mActivityWindowInfoChangedListeners.remove(listener); + } + } + + /** + * Called when receives a {@link ClientTransaction} that is updating an activity's + * {@link ActivityWindowInfo}. + */ + public void onActivityWindowInfoChanged(@NonNull IBinder activityToken, + @NonNull ActivityWindowInfo activityWindowInfo) { + if (!activityWindowInfoFlag()) { + return; + } + final Object[] activityWindowInfoChangedListeners; + synchronized (mLock) { + if (mActivityWindowInfoChangedListeners.isEmpty()) { + return; + } + activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray(); + } + for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) { + ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener) + .accept(activityToken, activityWindowInfo); + } + } + + /** * Called when receives a {@link ClientTransaction} that is updating display-related * window configuration. */ diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java index f9cf075d6062..b0213d7356df 100644 --- a/core/java/android/app/servertransaction/DestroyActivityItem.java +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -33,7 +33,6 @@ import android.os.Trace; public class DestroyActivityItem extends ActivityLifecycleItem { private boolean mFinished; - private int mConfigChanges; @Override public void preExecute(@NonNull ClientTransactionHandler client) { @@ -44,7 +43,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); - client.handleDestroyActivity(r, mFinished, mConfigChanges, + client.handleDestroyActivity(r, mFinished, false /* getNonConfigInstance */, "DestroyActivityItem"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -67,15 +66,13 @@ public class DestroyActivityItem extends ActivityLifecycleItem { /** Obtain an instance initialized with provided params. */ @NonNull - public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished, - int configChanges) { + public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished) { DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class); if (instance == null) { instance = new DestroyActivityItem(); } instance.setActivityToken(activityToken); instance.mFinished = finished; - instance.mConfigChanges = configChanges; return instance; } @@ -84,7 +81,6 @@ public class DestroyActivityItem extends ActivityLifecycleItem { public void recycle() { super.recycle(); mFinished = false; - mConfigChanges = 0; ObjectPool.recycle(this); } @@ -95,14 +91,12 @@ public class DestroyActivityItem extends ActivityLifecycleItem { public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); - dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private DestroyActivityItem(@NonNull Parcel in) { super(in); mFinished = in.readBoolean(); - mConfigChanges = in.readInt(); } public static final @NonNull Creator<DestroyActivityItem> CREATOR = new Creator<>() { @@ -124,7 +118,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { return false; } final DestroyActivityItem other = (DestroyActivityItem) o; - return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges; + return mFinished == other.mFinished; } @Override @@ -132,14 +126,12 @@ public class DestroyActivityItem extends ActivityLifecycleItem { int result = 17; result = 31 * result + super.hashCode(); result = 31 * result + (mFinished ? 1 : 0); - result = 31 * result + mConfigChanges; return result; } @Override public String toString() { return "DestroyActivityItem{" + super.toString() - + ",finished=" + mFinished - + ",mConfigChanges=" + mConfigChanges + "}"; + + ",finished=" + mFinished + "}"; } } diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index 8f1e90b985e6..d230284287b6 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -37,7 +37,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { private boolean mFinished; private boolean mUserLeaving; - private int mConfigChanges; private boolean mDontReport; private boolean mAutoEnteringPip; @@ -45,7 +44,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip, + client.handlePauseActivity(r, mFinished, mUserLeaving, mAutoEnteringPip, pendingActions, "PAUSE_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -72,7 +71,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Obtain an instance initialized with provided params. */ @NonNull public static PauseActivityItem obtain(@NonNull IBinder activityToken, boolean finished, - boolean userLeaving, int configChanges, boolean dontReport, boolean autoEnteringPip) { + boolean userLeaving, boolean dontReport, boolean autoEnteringPip) { PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class); if (instance == null) { instance = new PauseActivityItem(); @@ -80,7 +79,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { instance.setActivityToken(activityToken); instance.mFinished = finished; instance.mUserLeaving = userLeaving; - instance.mConfigChanges = configChanges; instance.mDontReport = dontReport; instance.mAutoEnteringPip = autoEnteringPip; @@ -91,7 +89,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { @NonNull public static PauseActivityItem obtain(@NonNull IBinder activityToken) { return obtain(activityToken, false /* finished */, false /* userLeaving */, - 0 /* configChanges */, true /* dontReport */, false /* autoEnteringPip*/); + true /* dontReport */, false /* autoEnteringPip*/); } @Override @@ -99,7 +97,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { super.recycle(); mFinished = false; mUserLeaving = false; - mConfigChanges = 0; mDontReport = false; mAutoEnteringPip = false; ObjectPool.recycle(this); @@ -113,7 +110,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeBoolean(mUserLeaving); - dest.writeInt(mConfigChanges); dest.writeBoolean(mDontReport); dest.writeBoolean(mAutoEnteringPip); } @@ -123,7 +119,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { super(in); mFinished = in.readBoolean(); mUserLeaving = in.readBoolean(); - mConfigChanges = in.readInt(); mDontReport = in.readBoolean(); mAutoEnteringPip = in.readBoolean(); } @@ -148,7 +143,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { } final PauseActivityItem other = (PauseActivityItem) o; return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving - && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport + && mDontReport == other.mDontReport && mAutoEnteringPip == other.mAutoEnteringPip; } @@ -158,7 +153,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { result = 31 * result + super.hashCode(); result = 31 * result + (mFinished ? 1 : 0); result = 31 * result + (mUserLeaving ? 1 : 0); - result = 31 * result + mConfigChanges; result = 31 * result + (mDontReport ? 1 : 0); result = 31 * result + (mAutoEnteringPip ? 1 : 0); return result; @@ -169,7 +163,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { return "PauseActivityItem{" + super.toString() + ",finished=" + mFinished + ",userLeaving=" + mUserLeaving - + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport + ",autoEnteringPip=" + mAutoEnteringPip + "}"; } diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index b8ce52da5a0c..def7b3fd9987 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -19,7 +19,6 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.os.IBinder; @@ -34,13 +33,11 @@ public class StopActivityItem extends ActivityLifecycleItem { private static final String TAG = "StopActivityItem"; - private int mConfigChanges; - @Override public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - client.handleStopActivity(r, mConfigChanges, pendingActions, + client.handleStopActivity(r, pendingActions, true /* finalStateRequest */, "STOP_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -63,16 +60,14 @@ public class StopActivityItem extends ActivityLifecycleItem { /** * Obtain an instance initialized with provided params. * @param activityToken the activity that stops. - * @param configChanges Configuration pieces that changed. */ @NonNull - public static StopActivityItem obtain(@NonNull IBinder activityToken, int configChanges) { + public static StopActivityItem obtain(@NonNull IBinder activityToken) { StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class); if (instance == null) { instance = new StopActivityItem(); } instance.setActivityToken(activityToken); - instance.mConfigChanges = configChanges; return instance; } @@ -80,23 +75,14 @@ public class StopActivityItem extends ActivityLifecycleItem { @Override public void recycle() { super.recycle(); - mConfigChanges = 0; ObjectPool.recycle(this); } // Parcelable implementation - /** Write to Parcel. */ - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mConfigChanges); - } - /** Read from Parcel. */ private StopActivityItem(@NonNull Parcel in) { super(in); - mConfigChanges = in.readInt(); } public static final @NonNull Creator<StopActivityItem> CREATOR = new Creator<>() { @@ -110,28 +96,7 @@ public class StopActivityItem extends ActivityLifecycleItem { }; @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (!super.equals(o)) { - return false; - } - final StopActivityItem other = (StopActivityItem) o; - return mConfigChanges == other.mConfigChanges; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + super.hashCode(); - result = 31 * result + mConfigChanges; - return result; - } - - @Override public String toString() { - return "StopActivityItem{" + super.toString() - + ",configChanges=" + mConfigChanges + "}"; + return "StopActivityItem{" + super.toString() + "}"; } } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index fa73c99be2b8..c83719149821 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -334,18 +334,18 @@ public class TransactionExecutor { break; case ON_PAUSE: mTransactionHandler.handlePauseActivity(r, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, + false /* userLeaving */, false /* autoEnteringPip */, mPendingActions, "LIFECYCLER_PAUSE_ACTIVITY"); break; case ON_STOP: - mTransactionHandler.handleStopActivity(r, 0 /* configChanges */, + mTransactionHandler.handleStopActivity(r, mPendingActions, false /* finalStateRequest */, "LIFECYCLER_STOP_ACTIVITY"); break; case ON_DESTROY: mTransactionHandler.handleDestroyActivity(r, false /* finishing */, - 0 /* configChanges */, false /* getNonConfigInstance */, + false /* getNonConfigInstance */, "performLifecycleSequence. cycling to:" + path.get(size - 1)); break; case ON_RESTART: diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java index 475c6fb9a48a..710261ab4c97 100644 --- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java +++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java @@ -200,7 +200,7 @@ public class TransactionExecutorHelper { lifecycleItem = PauseActivityItem.obtain(r.token); break; case ON_STOP: - lifecycleItem = StopActivityItem.obtain(r.token, 0 /* configChanges */); + lifecycleItem = StopActivityItem.obtain(r.token); break; default: lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */, diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 5e9d8f0a9b7e..610057bffdbf 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -208,6 +208,14 @@ flag { } flag { + name: "restrict_nonpreloads_system_shareduids" + namespace: "package_manager_service" + description: "Feature flag to restrict apps from joining system shared uids" + bug: "308573169" + is_fixed_read_only: true +} + +flag { name: "min_target_sdk_24" namespace: "responsible_apis" description: "Feature flag to bump min target sdk to 24" diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 57b437f4bacf..dc8f4b448931 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -3521,7 +3521,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed * to be supported by the camera HAL in the secure camera mode. Any other format or * resolutions might not be supported. Use - * {@link CameraManager#isSessionConfigurationWithParametersSupported } + * {@link CameraDevice#isSessionConfigurationSupported } * API to query if a secure session configuration is supported if the device supports this * API.</p> * <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application @@ -5046,18 +5046,18 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>The version of the session configuration query - * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported } + * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported } * API</p> * <p>The possible values in this key correspond to the values defined in * android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the * camera device must reliably report whether they are supported via - * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported } + * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported } * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p> * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support - * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }. + * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }. * Calling the method for this camera ID throws an UnsupportedOperationException.</p> * <p>If set to VANILLA_ICE_CREAM, the application can call - * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported } + * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported } * to check if the combinations of below features are supported.</p> * <ul> * <li>A subset of LIMITED-level device stream combinations.</li> @@ -6082,11 +6082,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Minimum and maximum padding zoom factors supported by this camera device for - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for the + * android.efv.paddingZoomFactor used for the * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } * extension.</p> * <p>The minimum and maximum padding zoom factors supported by the device for - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the + * android.efv.paddingZoomFactor used as part of the * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } * extension feature. This extension specific camera characteristic can be queried using * {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p> diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index e24c98e98c5d..9fb561bb4211 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -911,10 +911,10 @@ public abstract class CameraMetadata<TKey> { * </ul> * <p>Combinations of logical and physical streams, or physical streams from different * physical cameras are not guaranteed. However, if the camera device supports - * {@link CameraManager#isSessionConfigurationWithParametersSupported }, + * {@link CameraDevice#isSessionConfigurationSupported }, * application must be able to query whether a stream combination involving physical * streams is supported by calling - * {@link CameraManager#isSessionConfigurationWithParametersSupported }.</p> + * {@link CameraDevice#isSessionConfigurationSupported }.</p> * <p>Camera application shouldn't assume that there are at most 1 rear camera and 1 front * camera in the system. For an application that switches between front and back cameras, * the recommendation is to switch between the first rear camera and the first front diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index 9fbe348f1e9a..f3b7b919d87d 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -260,7 +260,7 @@ public final class MandatoryStreamCombination { * smaller sizes, then the resulting * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can * be tested either by calling {@link CameraDevice#createCaptureSession} or - * {@link CameraManager#isSessionConfigurationWithParametersSupported}. + * {@link CameraDeviceSetup#isSessionConfigurationSupported}. * * @return non-modifiable ascending list of available sizes. */ diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 1f5495999416..2816f777e8ab 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -27,6 +27,7 @@ import android.hardware.input.IKeyboardBacklightListener; import android.hardware.input.IKeyboardBacklightState; import android.hardware.input.IStickyModifierStateListener; import android.hardware.input.ITabletModeChangedListener; +import android.hardware.input.KeyboardLayoutSelectionResult; import android.hardware.input.TouchCalibration; import android.os.CombinedVibration; import android.hardware.input.IInputSensorEventListener; @@ -120,8 +121,9 @@ interface IInputManager { String keyboardLayoutDescriptor); // New Keyboard layout config APIs - String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId, - in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype); + KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( + in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo, + in InputMethodSubtype imeSubtype); @EnforcePermission("SET_KEYBOARD_LAYOUT") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 744dfae97108..a1242fb43bbd 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -784,10 +784,10 @@ public final class InputManager { * * @hide */ - @Nullable - public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier, - @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, - @Nullable InputMethodSubtype imeSubtype) { + @NonNull + public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( + @NonNull InputDeviceIdentifier identifier, @UserIdInt int userId, + @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { try { return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype); } catch (RemoteException ex) { diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl new file mode 100644 index 000000000000..13be2ff5ffb7 --- /dev/null +++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2024 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.hardware.input; + +parcelable KeyboardLayoutSelectionResult; diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java new file mode 100644 index 000000000000..5a1c9478f629 --- /dev/null +++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java @@ -0,0 +1,260 @@ +/* + * Copyright 2024 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.hardware.input; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcelable; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.internal.util.DataClass; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Provides information about the selected layout and the selection criteria when the caller calls + * {@link InputManager#getKeyboardLayoutForInputDevice(InputDeviceIdentifier, int, InputMethodInfo, + * InputMethodSubtype)} + * + * @hide + */ + +@DataClass(genParcelable = true, genToString = true, genEqualsHashCode = true) +public final class KeyboardLayoutSelectionResult implements Parcelable { + @Nullable + private final String mLayoutDescriptor; + + /** Unspecified layout selection criteria */ + public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0; + + /** Manual selection by user */ + public static final int LAYOUT_SELECTION_CRITERIA_USER = 1; + + /** Auto-detection based on device provided language tag and layout type */ + public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2; + + /** Auto-detection based on IME provided language tag and layout type */ + public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3; + + /** Default selection */ + public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4; + + /** Failed layout selection */ + public static final KeyboardLayoutSelectionResult FAILED = new KeyboardLayoutSelectionResult( + null, LAYOUT_SELECTION_CRITERIA_UNSPECIFIED); + + @LayoutSelectionCriteria + private final int mSelectionCriteria; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @IntDef(prefix = "LAYOUT_SELECTION_CRITERIA_", value = { + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED, + LAYOUT_SELECTION_CRITERIA_USER, + LAYOUT_SELECTION_CRITERIA_DEVICE, + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, + LAYOUT_SELECTION_CRITERIA_DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface LayoutSelectionCriteria {} + + @DataClass.Generated.Member + public static String layoutSelectionCriteriaToString(@LayoutSelectionCriteria int value) { + switch (value) { + case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED: + return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED"; + case LAYOUT_SELECTION_CRITERIA_USER: + return "LAYOUT_SELECTION_CRITERIA_USER"; + case LAYOUT_SELECTION_CRITERIA_DEVICE: + return "LAYOUT_SELECTION_CRITERIA_DEVICE"; + case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD: + return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD"; + case LAYOUT_SELECTION_CRITERIA_DEFAULT: + return "LAYOUT_SELECTION_CRITERIA_DEFAULT"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + public KeyboardLayoutSelectionResult( + @Nullable String layoutDescriptor, + @LayoutSelectionCriteria int selectionCriteria) { + this.mLayoutDescriptor = layoutDescriptor; + this.mSelectionCriteria = selectionCriteria; + + if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) { + throw new java.lang.IllegalArgumentException( + "selectionCriteria was " + mSelectionCriteria + " but must be one of: " + + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), " + + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), " + + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), " + + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), " + + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @Nullable String getLayoutDescriptor() { + return mLayoutDescriptor; + } + + @DataClass.Generated.Member + public @LayoutSelectionCriteria int getSelectionCriteria() { + return mSelectionCriteria; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "KeyboardLayoutSelectionResult { " + + "layoutDescriptor = " + mLayoutDescriptor + ", " + + "selectionCriteria = " + layoutSelectionCriteriaToString(mSelectionCriteria) + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(KeyboardLayoutSelectionResult other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + KeyboardLayoutSelectionResult that = (KeyboardLayoutSelectionResult) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mLayoutDescriptor, that.mLayoutDescriptor) + && mSelectionCriteria == that.mSelectionCriteria; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mLayoutDescriptor); + _hash = 31 * _hash + mSelectionCriteria; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mLayoutDescriptor != null) flg |= 0x1; + dest.writeByte(flg); + if (mLayoutDescriptor != null) dest.writeString(mLayoutDescriptor); + dest.writeInt(mSelectionCriteria); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ KeyboardLayoutSelectionResult(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + String layoutDescriptor = (flg & 0x1) == 0 ? null : in.readString(); + int selectionCriteria = in.readInt(); + + this.mLayoutDescriptor = layoutDescriptor; + this.mSelectionCriteria = selectionCriteria; + + if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) { + throw new java.lang.IllegalArgumentException( + "selectionCriteria was " + mSelectionCriteria + " but must be one of: " + + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), " + + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), " + + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), " + + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), " + + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<KeyboardLayoutSelectionResult> CREATOR + = new Parcelable.Creator<KeyboardLayoutSelectionResult>() { + @Override + public KeyboardLayoutSelectionResult[] newArray(int size) { + return new KeyboardLayoutSelectionResult[size]; + } + + @Override + public KeyboardLayoutSelectionResult createFromParcel(@NonNull android.os.Parcel in) { + return new KeyboardLayoutSelectionResult(in); + } + }; + + @DataClass.Generated( + time = 1709568115865L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java", + inputSignatures = "private final @android.annotation.Nullable java.lang.String mLayoutDescriptor\npublic static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED\npublic static final int LAYOUT_SELECTION_CRITERIA_USER\npublic static final int LAYOUT_SELECTION_CRITERIA_DEVICE\npublic static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD\npublic static final int LAYOUT_SELECTION_CRITERIA_DEFAULT\npublic static final android.hardware.input.KeyboardLayoutSelectionResult FAILED\nprivate final @android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria int mSelectionCriteria\nclass KeyboardLayoutSelectionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java index 8a44d4fa5934..798203fadb4d 100644 --- a/core/java/android/view/ScrollFeedbackProvider.java +++ b/core/java/android/view/ScrollFeedbackProvider.java @@ -21,8 +21,11 @@ import android.annotation.NonNull; import android.view.flags.Flags; /** - * Interface to represent an entity giving consistent feedback for different events surrounding view - * scroll. + * Provides feedback to the user for scroll events on a {@link View}. The type of feedback provided + * to the user may depend on the {@link InputDevice} that generated the scroll events. + * + * <p>An example of the type of feedback that this interface may provide is haptic feedback (that + * is, tactile feedback that provide the user physical feedback for their scroll). * * <p>The interface provides methods for the client to report different scroll events. The client * should report all scroll events that they want to be considered for scroll feedback using the diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 0e5747d0e445..fe4ac4e2f0f1 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1105,6 +1105,7 @@ public class RemoteViews implements Parcelable, Filter { SetRemoteCollectionItemListAdapterAction(Parcel parcel) { mViewId = parcel.readInt(); mIntentId = parcel.readInt(); + mIsReplacedIntoAction = parcel.readBoolean(); mServiceIntent = parcel.readTypedObject(Intent.CREATOR); mItems = mServiceIntent != null ? null @@ -1128,6 +1129,7 @@ public class RemoteViews implements Parcelable, Filter { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mViewId); dest.writeInt(mIntentId); + dest.writeBoolean(mIsReplacedIntoAction); dest.writeTypedObject(mServiceIntent, flags); if (mItems != null) { mItems.writeToParcel(dest, flags, /* attached= */ true); @@ -1209,6 +1211,19 @@ public class RemoteViews implements Parcelable, Filter { } /** + * The maximum size for RemoteViews with converted RemoteCollectionItemsAdapter. + * When converting RemoteViewsAdapter to RemoteCollectionItemsAdapter, we want to put size + * limits on each unique RemoteCollectionItems in order to not exceed the transaction size limit + * for each parcel (typically 1 MB). We leave a certain ratio of the maximum size as a buffer + * for missing calculations of certain parameters (e.g. writing a RemoteCollectionItems to the + * parcel will write its Id array as well, but that is missing when writing itschild RemoteViews + * directly to the parcel as we did in RemoteViewsService) + * + * @hide + */ + private static final int MAX_SINGLE_PARCEL_SIZE = (int) (1_000_000 * 0.8); + + /** * @hide */ public CompletableFuture<Void> collectAllIntents() { @@ -1260,17 +1275,47 @@ public class RemoteViews implements Parcelable, Filter { return mUriToCollectionMapping.get(uri); } - CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) { - CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null); + public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete( + @NonNull RemoteViews inViews) { + SparseArray<Intent> idToIntentMapping = new SparseArray<>(); + // Collect the number of uinque Intent (which is equal to the number of new connections + // to make) for size allocation and exclude certain collections from being written to + // the parcel to better estimate the space left for reallocation. + collectAllIntentsInternal(inViews, idToIntentMapping); + + // Calculate the individual size here + int numOfIntents = idToIntentMapping.size(); + if (numOfIntents == 0) { + Log.e(LOG_TAG, "Possibly notifying updates for nonexistent view Id"); + return CompletableFuture.completedFuture(null); + } + + Parcel sizeTestParcel = Parcel.obtain(); + // Write self RemoteViews to the parcel, which includes the actions/bitmaps/collection + // cache to see how much space is left for the RemoteCollectionItems that are to be + // updated. + RemoteViews.this.writeToParcel(sizeTestParcel, + /* flags= */ 0, + /* intentsToIgnore= */ idToIntentMapping); + int remainingSize = MAX_SINGLE_PARCEL_SIZE - sizeTestParcel.dataSize(); + sizeTestParcel.recycle(); + + int individualSize = remainingSize < 0 + ? 0 + : remainingSize / numOfIntents; + + return connectAllUniqueIntents(individualSize, idToIntentMapping); + } + + private void collectAllIntentsInternal(@NonNull RemoteViews inViews, + @NonNull SparseArray<Intent> idToIntentMapping) { if (inViews.hasSizedRemoteViews()) { for (RemoteViews remoteViews : inViews.mSizedRemoteViews) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(remoteViews)); + collectAllIntentsInternal(remoteViews, idToIntentMapping); } } else if (inViews.hasLandscapeAndPortraitLayouts()) { - collectionFuture = CompletableFuture.allOf( - collectAllIntentsNoComplete(inViews.mLandscape), - collectAllIntentsNoComplete(inViews.mPortrait)); + collectAllIntentsInternal(inViews.mLandscape, idToIntentMapping); + collectAllIntentsInternal(inViews.mPortrait, idToIntentMapping); } else if (inViews.mActions != null) { for (Action action : inViews.mActions) { if (action instanceof SetRemoteCollectionItemListAdapterAction rca) { @@ -1280,13 +1325,16 @@ public class RemoteViews implements Parcelable, Filter { } if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) { - final String uri = mIdToUriMapping.get(rca.mIntentId); - collectionFuture = CompletableFuture.allOf(collectionFuture, - getItemsFutureFromIntentWithTimeout(rca.mServiceIntent) - .thenAccept(rc -> { - rc.setHierarchyRootData(getHierarchyRootData()); - mUriToCollectionMapping.put(uri, rc); - })); + rca.mIsReplacedIntoAction = false; + + // Avoid redundant connections for the same intent. Also making sure + // that the number of connections we are making is always equal to the + // nmuber of unique intents that are being used for the updates. + if (idToIntentMapping.contains(rca.mIntentId)) { + continue; + } + + idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent); rca.mItems = null; continue; } @@ -1295,7 +1343,7 @@ public class RemoteViews implements Parcelable, Filter { // intents. if (rca.mServiceIntent != null) { final String uri = rca.mServiceIntent.toUri(0); - int index = mIdToUriMapping.indexOfValue(uri); + int index = mIdToUriMapping.indexOfValueByValue(uri); if (index == -1) { int newIntentId = mIdToUriMapping.size(); rca.mIntentId = newIntentId; @@ -1305,41 +1353,50 @@ public class RemoteViews implements Parcelable, Filter { rca.mItems = null; continue; } - collectionFuture = CompletableFuture.allOf(collectionFuture, - getItemsFutureFromIntentWithTimeout(rca.mServiceIntent) - .thenAccept(rc -> { - rc.setHierarchyRootData(getHierarchyRootData()); - mUriToCollectionMapping.put(uri, rc); - })); + + idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent); rca.mItems = null; } else { for (RemoteViews views : rca.mItems.mViews) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(views)); + collectAllIntentsInternal(views, idToIntentMapping); } } } else if (action instanceof ViewGroupActionAdd vgaa && vgaa.mNestedViews != null) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(vgaa.mNestedViews)); + collectAllIntentsInternal(vgaa.mNestedViews, idToIntentMapping); } } } + } - return collectionFuture; + private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize, + @NonNull SparseArray<Intent> idToIntentMapping) { + List<CompletableFuture<Void>> intentFutureList = new ArrayList<>(); + for (int i = 0; i < idToIntentMapping.size(); i++) { + String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i)); + Intent currentIntent = idToIntentMapping.valueAt(i); + intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent, + individualSize) + .thenAccept(items -> { + items.setHierarchyRootData(getHierarchyRootData()); + mUriToCollectionMapping.put(currentIntentUri, items); + })); + } + + return CompletableFuture.allOf(intentFutureList.toArray(CompletableFuture[]::new)); } private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout( - Intent intent) { + Intent intent, int individualSize) { if (intent == null) { Log.e(LOG_TAG, "Null intent received when generating adapter future"); return CompletableFuture.completedFuture(new RemoteCollectionItems - .Builder().build()); + .Builder().build()); } final Context context = ActivityThread.currentApplication(); - final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); + final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), result.defaultExecutor(), new ServiceConnection() { @Override @@ -1348,11 +1405,11 @@ public class RemoteViews implements Parcelable, Filter { RemoteCollectionItems items; try { items = IRemoteViewsFactory.Stub.asInterface(iBinder) - .getRemoteCollectionItems(); + .getRemoteCollectionItems(individualSize); } catch (RemoteException re) { items = new RemoteCollectionItems.Builder().build(); - Log.e(LOG_TAG, "Error getting collection items from the factory", - re); + Log.e(LOG_TAG, "Error getting collection items from the" + + " factory", re); } finally { context.unbindService(this); } @@ -1371,10 +1428,17 @@ public class RemoteViews implements Parcelable, Filter { return result; } - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(Parcel out, int flags, + @Nullable SparseArray<Intent> intentsToIgnore) { out.writeInt(mIdToUriMapping.size()); for (int i = 0; i < mIdToUriMapping.size(); i++) { - out.writeInt(mIdToUriMapping.keyAt(i)); + int currentIntentId = mIdToUriMapping.keyAt(i); + if (intentsToIgnore != null && intentsToIgnore.contains(currentIntentId)) { + // Skip writing collections that are to be updated in the following steps to + // better estimate the RemoteViews size. + continue; + } + out.writeInt(currentIntentId); String intentUri = mIdToUriMapping.valueAt(i); out.writeString8(intentUri); mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true); @@ -6724,7 +6788,13 @@ public class RemoteViews implements Parcelable, Filter { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcel(dest, flags, /* intentsToIgnore= */ null); + } + + private void writeToParcel(Parcel dest, int flags, + @Nullable SparseArray<Intent> intentsToIgnore) { boolean prevSquashingAllowed = dest.allowSquashing(); if (!hasMultipleLayouts()) { @@ -6733,7 +6803,7 @@ public class RemoteViews implements Parcelable, Filter { // is shared by all children. if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } mApplication.writeToParcel(dest, flags); if (mIsRoot || mIdealSize == null) { @@ -6750,7 +6820,7 @@ public class RemoteViews implements Parcelable, Filter { dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS); if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } dest.writeInt(mSizedRemoteViews.size()); for (RemoteViews view : mSizedRemoteViews) { @@ -6762,7 +6832,7 @@ public class RemoteViews implements Parcelable, Filter { // is shared by all children. if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } mLandscape.writeToParcel(dest, flags); // Both RemoteViews already share the same package and user diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index a250a867b9de..d4a5bbd79d31 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -19,6 +19,7 @@ package android.widget; import android.app.Service; import android.content.Intent; import android.os.IBinder; +import android.os.Parcel; import com.android.internal.widget.IRemoteViewsFactory; @@ -43,13 +44,6 @@ public abstract class RemoteViewsService extends Service { private static final Object sLock = new Object(); /** - * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory - * - * @hide - */ - private static final int MAX_NUM_ENTRY = 10; - - /** * An interface for an adapter between a remote collection view (ListView, GridView, etc) and * the underlying data for that view. The implementor is responsible for making a RemoteView * for each item in the data set. This interface is a thin wrapper around {@link Adapter}. @@ -235,9 +229,10 @@ public abstract class RemoteViewsService extends Service { } @Override - public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { + public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); + Parcel capSizeTestParcel = Parcel.obtain(); try { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = @@ -245,15 +240,25 @@ public abstract class RemoteViewsService extends Service { mFactory.onDataSetChanged(); itemsBuilder.setHasStableIds(mFactory.hasStableIds()); - final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY); + final int numOfEntries = mFactory.getCount(); + for (int i = 0; i < numOfEntries; i++) { - itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i)); + final long currentItemId = mFactory.getItemId(i); + final RemoteViews currentView = mFactory.getViewAt(i); + currentView.writeToParcel(capSizeTestParcel, 0); + if (capSizeTestParcel.dataSize() > capSize) { + break; + } + itemsBuilder.addItem(currentItemId, currentView); } items = itemsBuilder.build(); } catch (Exception ex) { Thread t = Thread.currentThread(); Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); + } finally { + // Recycle the parcel + capSizeTestParcel.recycle(); } return items; } diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl index 918d9c029ef5..1d0e97295571 100644 --- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl +++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl @@ -39,6 +39,6 @@ interface IRemoteViewsFactory { boolean hasStableIds(); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean isCreated(); - RemoteViews.RemoteCollectionItems getRemoteCollectionItems(); + RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 487b5be9ef5c..ba9751fe1d12 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8621,6 +8621,10 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> + <service android:name="com.android.system.virtualmachine.SecretkeeperJobService" + android:permission="android.permission.BIND_JOB_SERVICE"> + </service> + <service android:name="com.android.server.PruneInstantAppsJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 5e3e1b0bbb43..6e56fe2abfdd 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -334,4 +334,9 @@ <bool name="config_enable_cellular_on_boot_default">true</bool> <java-symbol type="bool" name="config_enable_cellular_on_boot_default" /> + <!-- Defines metrics pull cooldown period. The default cooldown period is 23 hours, + some Telephony metrics need to be pulled more frequently --> + <integer name="config_metrics_pull_cooldown_millis">82800000</integer> + <java-symbol type="integer" name="config_metrics_pull_cooldown_millis" /> + </resources> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index d115bf306b45..927c67cb1d36 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -21,16 +21,22 @@ import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.annotation.NonNull; @@ -46,6 +52,7 @@ import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.ClientTransactionListenerController; import android.app.servertransaction.ConfigurationChangeItem; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.ResumeActivityItem; @@ -60,7 +67,9 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.DisplayMetrics; import android.util.MergedConfiguration; import android.view.Display; @@ -81,11 +90,14 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -103,11 +115,17 @@ public class ActivityThreadTest { // few sequence numbers the framework used to launch the test activity. private static final int BASE_SEQ = 10000000; - @Rule + @Rule(order = 0) public final ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, false /* launchActivity */); + @Rule(order = 1) + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + @Mock + private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener; + private WindowTokenClientController mOriginalWindowTokenClientController; private Configuration mOriginalAppConfig; @@ -115,6 +133,8 @@ public class ActivityThreadTest { @Before public void setup() { + MockitoAnnotations.initMocks(this); + // Keep track of the original controller, so that it can be used to restore in tearDown() // when there is override in some test cases. mOriginalWindowTokenClientController = WindowTokenClientController.getInstance(); @@ -129,6 +149,8 @@ public class ActivityThreadTest { mCreatedVirtualDisplays = null; } WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); + ClientTransactionListenerController.getInstance() + .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig)); } @@ -783,6 +805,101 @@ public class ActivityThreadTest { verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken); } + @Test + public void testActivityWindowInfoChanged_activityLaunch() { + mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( + mActivityWindowInfoListener); + + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity); + + verify(mActivityWindowInfoListener).accept(activityClientRecord.token, + activityClientRecord.getActivityWindowInfo()); + } + + @Test + public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( + mActivityWindowInfoListener); + + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity); + + // The same ActivityWindowInfo won't trigger duplicated callback. + verify(mActivityWindowInfoListener).accept(activityClientRecord.token, + activityClientRecord.getActivityWindowInfo()); + + final Configuration currentConfig = activity.getResources().getConfiguration(); + final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); + activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000), + new Rect(0, 0, 1000, 1000)); + final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain( + activity.getActivityToken(), null, null, 0, + new MergedConfiguration(currentConfig, currentConfig), + false /* preserveWindow */, activityWindowInfo); + final ClientTransaction transaction = newTransaction(activity); + transaction.addTransactionItem(relaunchItem); + appThread.scheduleTransaction(transaction); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + verify(mActivityWindowInfoListener).accept(activityClientRecord.token, + activityWindowInfo); + } + + @Test + public void testActivityWindowInfoChanged_activityConfigurationChanged() + throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( + mActivityWindowInfoListener); + + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + clearInvocations(mActivityWindowInfoListener); + final Configuration config = new Configuration(activity.getResources().getConfiguration()); + config.seq++; + final Rect taskBounds = new Rect(0, 0, 1000, 2000); + final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000); + final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); + activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds); + final ActivityConfigurationChangeItem activityConfigurationChangeItem = + ActivityConfigurationChangeItem.obtain( + activity.getActivityToken(), config, activityWindowInfo); + final ClientTransaction transaction = newTransaction(activity); + transaction.addTransactionItem(activityConfigurationChangeItem); + appThread.scheduleTransaction(transaction); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + verify(mActivityWindowInfoListener).accept(activity.getActivityToken(), + activityWindowInfo); + + clearInvocations(mActivityWindowInfoListener); + final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo(); + activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds); + config.seq++; + final ActivityConfigurationChangeItem activityConfigurationChangeItem2 = + ActivityConfigurationChangeItem.obtain( + activity.getActivityToken(), config, activityWindowInfo2); + final ClientTransaction transaction2 = newTransaction(activity); + transaction2.addTransactionItem(activityConfigurationChangeItem2); + appThread.scheduleTransaction(transaction); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + // The same ActivityWindowInfo won't trigger duplicated callback. + verify(mActivityWindowInfoListener, never()).accept(any(), any()); + } + /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the @@ -871,7 +988,7 @@ public class ActivityThreadTest { @NonNull private static ClientTransaction newStopTransaction(@NonNull Activity activity) { final StopActivityItem stopStateRequest = StopActivityItem.obtain( - activity.getActivityToken(), 0 /* configChanges */); + activity.getActivityToken()); final ClientTransaction transaction = newTransaction(activity); transaction.addTransactionItem(stopStateRequest); diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java index 4db5d1bf4f67..990739745f24 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java @@ -136,7 +136,7 @@ public class ClientTransactionItemTest { @Test public void testDestroyActivityItem_preExecute() { final DestroyActivityItem item = DestroyActivityItem - .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + .obtain(mActivityToken, false /* finished */); item.preExecute(mHandler); assertEquals(1, mActivitiesToBeDestroyed.size()); @@ -146,7 +146,7 @@ public class ClientTransactionItemTest { @Test public void testDestroyActivityItem_postExecute() { final DestroyActivityItem item = DestroyActivityItem - .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + .obtain(mActivityToken, false /* finished */); item.preExecute(mHandler); item.postExecute(mHandler, mPendingActions); @@ -156,11 +156,11 @@ public class ClientTransactionItemTest { @Test public void testDestroyActivityItem_execute() { final DestroyActivityItem item = DestroyActivityItem - .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + .obtain(mActivityToken, false /* finished */); item.execute(mHandler, mActivityClientRecord, mPendingActions); verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */, - eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any()); + eq(false) /* getNonConfigInstance */, any()); } @Test diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 213fd7bd494d..77d31a5f27e7 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -22,21 +22,29 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.IDisplayManager; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.DisplayInfo; +import android.window.ActivityWindowInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.window.flags.Flags; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -44,6 +52,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.BiConsumer; + /** * Tests for {@link ClientTransactionListenerController}. * @@ -62,6 +72,10 @@ public class ClientTransactionListenerControllerTest { private IDisplayManager mIDisplayManager; @Mock private DisplayManager.DisplayListener mListener; + @Mock + private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener; + @Mock + private IBinder mActivityToken; private DisplayManagerGlobal mDisplayManager; private Handler mHandler; @@ -91,4 +105,24 @@ public class ClientTransactionListenerControllerTest { verify(mListener).onDisplayChanged(123); } + + @Test + public void testActivityWindowInfoChangedListener() { + mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener); + final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); + activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000), + new Rect(0, 0, 1000, 1000)); + mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo); + + verify(mActivityWindowInfoListener).accept(mActivityToken, activityWindowInfo); + + clearInvocations(mActivityWindowInfoListener); + mController.unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); + + mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo); + + verify(mActivityWindowInfoListener, never()).accept(any(), any()); + } } diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 31ea6759c710..584fe16d00ac 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -98,7 +98,7 @@ public class ObjectPoolTests { @Test public void testRecycleDestroyActivityItem() { - testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117)); + testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true)); } @Test @@ -169,7 +169,7 @@ public class ObjectPoolTests { @Test public void testRecyclePauseActivityItemItem() { - testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true)); + testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, true, true)); } @Test @@ -185,7 +185,7 @@ public class ObjectPoolTests { @Test public void testRecycleStopItem() { - testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4)); + testRecycle(() -> StopActivityItem.obtain(mActivityToken)); } @Test diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index adb6f2a23847..935bc7565986 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -306,7 +306,7 @@ public class TransactionExecutorTests { final IBinder token = mock(IBinder.class); final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */); destroyTransaction.addTransactionItem( - DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */)); + DestroyActivityItem.obtain(token, false /* finished */)); destroyTransaction.preExecute(mTransactionHandler); // The activity should be added to to-be-destroyed container. assertEquals(1, activitiesToBeDestroyed.size()); diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 75347bf2c8de..d451fe58e8e4 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -155,8 +155,7 @@ public class TransactionParcelTests { @Test public void testDestroy() { - DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */, - 135 /* configChanges */); + DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */); writeAndPrepareForReading(item); // Read from parcel and assert @@ -244,8 +243,7 @@ public class TransactionParcelTests { public void testPause() { // Write to parcel PauseActivityItem item = PauseActivityItem.obtain(mActivityToken, true /* finished */, - true /* userLeaving */, 135 /* configChanges */, true /* dontReport */, - true /* autoEnteringPip */); + true /* userLeaving */, true /* dontReport */, true /* autoEnteringPip */); writeAndPrepareForReading(item); // Read from parcel and assert @@ -272,7 +270,7 @@ public class TransactionParcelTests { @Test public void testStop() { // Write to parcel - StopActivityItem item = StopActivityItem.obtain(mActivityToken, 14 /* configChanges */); + StopActivityItem item = StopActivityItem.obtain(mActivityToken); writeAndPrepareForReading(item); // Read from parcel and assert @@ -305,8 +303,7 @@ public class TransactionParcelTests { ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain( mActivityToken, config(), new ActivityWindowInfo()); - StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken, - 78 /* configChanges */); + StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken); ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addTransactionItem(callback1); @@ -351,8 +348,7 @@ public class TransactionParcelTests { mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG); // Write to parcel - StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken, - 78 /* configChanges */); + StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken); ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addTransactionItem(lifecycleRequest); diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java index 4f722cefcf9f..6ab77dc9d535 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java @@ -355,7 +355,7 @@ public class RemoteViewsAdapterTest { } @Override - public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { + public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = new RemoteViews.RemoteCollectionItems.Builder(); itemsBuilder.setHasStableIds(hasStableIds()) diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index 66be05ff233c..ed641e048a81 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -309,17 +309,17 @@ public class ActivityThreadClientTest { private void pauseActivity(ActivityClientRecord r) { mThread.handlePauseActivity(r, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */, + false /* userLeaving */, false /* autoEnteringPip */, null /* pendingActions */, "test"); } private void stopActivity(ActivityClientRecord r) { - mThread.handleStopActivity(r, 0 /* configChanges */, + mThread.handleStopActivity(r, new PendingTransactionActions(), false /* finalStateRequest */, "test"); } private void destroyActivity(ActivityClientRecord r) { - mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */, + mThread.handleDestroyActivity(r, true /* finishing */, false /* getNonConfigInstance */, "test"); } diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 238a3e10f058..1410950966e9 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -72,6 +72,12 @@ prebuilt_etc { src: "enhanced-confirmation.xml", } +prebuilt_etc { + name: "package-shareduid-allowlist.xml", + sub_dir: "sysconfig", + src: "package-shareduid-allowlist.xml", +} + // Privapp permission whitelist files prebuilt_etc { diff --git a/data/etc/CleanSpec.mk b/data/etc/CleanSpec.mk index 783a7edadeb7..fd38d2782cb2 100644 --- a/data/etc/CleanSpec.mk +++ b/data/etc/CleanSpec.mk @@ -43,6 +43,8 @@ #$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/sysconfig/package-shareduid-allowlist.xml) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/sysconfig/package-shareduid-allowlist.xml) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.carrierconfig.xml) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.carrierconfig.xml) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.emergency.xml) diff --git a/data/etc/package-shareduid-allowlist.xml b/data/etc/package-shareduid-allowlist.xml new file mode 100644 index 000000000000..2401d4a26e68 --- /dev/null +++ b/data/etc/package-shareduid-allowlist.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> + +<!-- +This XML defines an allowlist for packages that want to join a particular shared-uid. +If a non-system package that is signed with platform signature, is trying to join a particular +shared-uid, and not in this list, the installation will fail. + +- The "package" XML attribute refers to the app's package name. +- The "shareduid" XML attribute refers to the shared uid name. + +Example usage + 1. <allow-package-shareduid package="com.example.app" shareduid="android.uid.system"/> + Indicates that a package - com.example.app, will be able to join android.uid.system. + 2. <allow-package-shareduid package="oem.example.app" shareduid="oem.uid.custom"/> + Indicates that a package - oem.example.app, will be able to join oem.uid.custom. +--> + +<config> + <allow-package-shareduid package="android.test.settings" shareduid="android.uid.system" /> +</config> diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml index 15ea15a9b4c1..53024ab858c4 100644 --- a/data/fonts/font_fallback.xml +++ b/data/fonts/font_fallback.xml @@ -792,14 +792,10 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal"> - NotoSerifHentaigana-EL.ttf + <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="700" style="normal"> - NotoSerifHentaigana-EL.ttf - <axis tag="wght" stylevalue="700"/> - </font> </family> <family lang="ko"> <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular"> diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml index c1ca67ef456a..ac1b06495832 100644 --- a/data/fonts/font_fallback_cjkvf.xml +++ b/data/fonts/font_fallback_cjkvf.xml @@ -804,14 +804,10 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal"> - NotoSerifHentaigana-EL.ttf + <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="700" style="normal"> - NotoSerifHentaigana-EL.ttf - <axis tag="wght" stylevalue="700"/> - </font> </family> <family lang="ko"> <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin" diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index b23f00554bdb..d1aa8e9734c2 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -1433,12 +1433,12 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal"> - NotoSerifHentaigana-EL.ttf + <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="700" style="normal"> - NotoSerifHentaigana-EL.ttf + <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="700"/> </font> </family> diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml index 1ab71ae270eb..9545ae718574 100644 --- a/data/fonts/fonts_cjkvf.xml +++ b/data/fonts/fonts_cjkvf.xml @@ -1532,12 +1532,12 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal"> - NotoSerifHentaigana-EL.ttf + <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="700" style="normal"> - NotoSerifHentaigana-EL.ttf + <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="700"/> </font> </family> diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 6da07198c3ad..884268a4b85c 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -29,11 +29,13 @@ import android.util.Log; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.NeverInline; import libcore.util.NativeAllocationRegistry; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Locale; import java.util.Objects; /** @@ -85,6 +87,30 @@ public class MeasuredText { return mChars; } + private void rangeCheck(int start, int end) { + if (start < 0 || start > end || end > mChars.length) { + throwRangeError(start, end); + } + } + + @NeverInline + private void throwRangeError(int start, int end) { + throw new IllegalArgumentException(String.format(Locale.US, + "start(%d) end(%d) length(%d) out of bounds", start, end, mChars.length)); + } + + private void offsetCheck(int offset) { + if (offset < 0 || offset >= mChars.length) { + throwOffsetError(offset); + } + } + + @NeverInline + private void throwOffsetError(int offset) { + throw new IllegalArgumentException(String.format(Locale.US, + "offset (%d) length(%d) out of bounds", offset, mChars.length)); + } + /** * Returns the width of a given range. * @@ -93,12 +119,7 @@ public class MeasuredText { */ public @FloatRange(from = 0.0) @Px float getWidth( @IntRange(from = 0) int start, @IntRange(from = 0) int end) { - Preconditions.checkArgument(0 <= start && start <= mChars.length, - "start(%d) must be 0 <= start <= %d", start, mChars.length); - Preconditions.checkArgument(0 <= end && end <= mChars.length, - "end(%d) must be 0 <= end <= %d", end, mChars.length); - Preconditions.checkArgument(start <= end, - "start(%d) is larger than end(%d)", start, end); + rangeCheck(start, end); return nGetWidth(mNativePtr, start, end); } @@ -120,12 +141,7 @@ public class MeasuredText { */ public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Rect rect) { - Preconditions.checkArgument(0 <= start && start <= mChars.length, - "start(%d) must be 0 <= start <= %d", start, mChars.length); - Preconditions.checkArgument(0 <= end && end <= mChars.length, - "end(%d) must be 0 <= end <= %d", end, mChars.length); - Preconditions.checkArgument(start <= end, - "start(%d) is larger than end(%d)", start, end); + rangeCheck(start, end); Preconditions.checkNotNull(rect); nGetBounds(mNativePtr, mChars, start, end, rect); } @@ -139,12 +155,7 @@ public class MeasuredText { */ public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Paint.FontMetricsInt outMetrics) { - Preconditions.checkArgument(0 <= start && start <= mChars.length, - "start(%d) must be 0 <= start <= %d", start, mChars.length); - Preconditions.checkArgument(0 <= end && end <= mChars.length, - "end(%d) must be 0 <= end <= %d", end, mChars.length); - Preconditions.checkArgument(start <= end, - "start(%d) is larger than end(%d)", start, end); + rangeCheck(start, end); Objects.requireNonNull(outMetrics); long packed = nGetExtent(mNativePtr, mChars, start, end); @@ -160,8 +171,7 @@ public class MeasuredText { * @param offset an offset of the character. */ public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) { - Preconditions.checkArgument(0 <= offset && offset < mChars.length, - "offset(%d) is larger than text length %d" + offset, mChars.length); + offsetCheck(offset); return nGetCharWidthAt(mNativePtr, offset); } 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 038d0081ead8..1abda4287800 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -56,6 +56,7 @@ import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; import android.app.Instrumentation; +import android.app.servertransaction.ClientTransactionListenerController; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -103,6 +104,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; +import java.util.function.BiConsumer; /** * Main controller class that manages split states and presentation. @@ -178,6 +180,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>(); + /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */ + @GuardedBy("mLock") + @Nullable + private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>> + mEmbeddedActivityWindowInfoCallback; + + /** Listener registered to {@link ClientTransactionListenerController}. */ + @GuardedBy("mLock") + @Nullable + private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener = + Flags.activityWindowInfoFlag() + ? this::onActivityWindowInfoChanged + : null; + private final Handler mHandler; final Object mLock = new Object(); private final ActivityStartMonitor mActivityStartMonitor; @@ -2456,6 +2472,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @VisibleForTesting + @Nullable + ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) { + return ActivityThread.currentActivityThread() + .getActivityClient(activity.getActivityToken()); + } + + @VisibleForTesting ActivityStartMonitor getActivityStartMonitor() { return mActivityStartMonitor; } @@ -2468,8 +2491,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @VisibleForTesting @Nullable IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) { - final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread() - .getActivityClient(activity.getActivityToken()); + final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity); return record != null ? record.mTaskFragmentToken : null; } @@ -2876,17 +2898,102 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + @Override + public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor, + @NonNull Consumer<EmbeddedActivityWindowInfo> callback) { + if (!Flags.activityWindowInfoFlag()) { + return; + } + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + synchronized (mLock) { + if (mEmbeddedActivityWindowInfoCallback == null) { + ClientTransactionListenerController.getInstance() + .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener()); + } + mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback); + } + } + + @Override + public void clearEmbeddedActivityWindowInfoCallback() { + if (!Flags.activityWindowInfoFlag()) { + return; + } + synchronized (mLock) { + if (mEmbeddedActivityWindowInfoCallback == null) { + return; + } + mEmbeddedActivityWindowInfoCallback = null; + ClientTransactionListenerController.getInstance() + .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener()); + } + } + + @VisibleForTesting + @GuardedBy("mLock") + @Nullable + BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() { + return mActivityWindowInfoListener; + } + + @Nullable + @Override + public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) { + if (!Flags.activityWindowInfoFlag()) { + return null; + } + synchronized (mLock) { + final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity); + return activityWindowInfo != null + ? translateActivityWindowInfo(activity, activityWindowInfo) + : null; + } + } + + @VisibleForTesting + void onActivityWindowInfoChanged(@NonNull IBinder activityToken, + @NonNull ActivityWindowInfo activityWindowInfo) { + synchronized (mLock) { + if (mEmbeddedActivityWindowInfoCallback == null) { + return; + } + final Executor executor = mEmbeddedActivityWindowInfoCallback.first; + final Consumer<EmbeddedActivityWindowInfo> callback = + mEmbeddedActivityWindowInfoCallback.second; + + final Activity activity = getActivity(activityToken); + if (activity == null) { + return; + } + final EmbeddedActivityWindowInfo info = translateActivityWindowInfo( + activity, activityWindowInfo); + + executor.execute(() -> callback.accept(info)); + } + } + @Nullable - private static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) { + private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) { if (activity.isFinishing()) { return null; } - final ActivityThread.ActivityClientRecord record = - ActivityThread.currentActivityThread() - .getActivityClient(activity.getActivityToken()); + final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity); return record != null ? record.getActivityWindowInfo() : null; } + @NonNull + private static EmbeddedActivityWindowInfo translateActivityWindowInfo( + @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) { + final boolean isEmbedded = activityWindowInfo.isEmbedded(); + final Rect activityBounds = new Rect(activity.getResources().getConfiguration() + .windowConfiguration.getBounds()); + final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds()); + final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds()); + return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds, + activityStackBounds); + } + /** * If the two rules have the same presentation, and the calculated {@link SplitAttributes} * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same 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 00f8b5925d66..bdeeb7304b12 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 @@ -72,6 +72,8 @@ import static org.mockito.Mockito.times; import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityOptions; +import android.app.ActivityThread; +import android.app.servertransaction.ClientTransactionListenerController; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -83,9 +85,11 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import android.view.WindowInsets; import android.view.WindowMetrics; +import android.window.ActivityWindowInfo; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentParentInfo; @@ -99,7 +103,10 @@ import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; +import com.android.window.flags.Flags; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -110,6 +117,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -127,6 +136,9 @@ public class SplitControllerTest { private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent( new ComponentName("test", "placeholder")); + @Rule + public final SetFlagsRule mSetFlagRule = new SetFlagsRule(); + private Activity mActivity; @Mock private Resources mActivityResources; @@ -138,6 +150,13 @@ public class SplitControllerTest { private Handler mHandler; @Mock private WindowLayoutComponentImpl mWindowLayoutComponent; + @Mock + private ActivityWindowInfo mActivityWindowInfo; + @Mock + private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener; + @Mock + private androidx.window.extensions.core.util.function.Consumer<EmbeddedActivityWindowInfo> + mEmbeddedActivityWindowInfoCallback; private SplitController mSplitController; private SplitPresenter mSplitPresenter; @@ -1529,6 +1548,73 @@ public class SplitControllerTest { .getTopNonFinishingActivity(), secondaryActivity); } + @Test + public void testIsActivityEmbedded() { + mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + assertFalse(mSplitController.isActivityEmbedded(mActivity)); + + doReturn(true).when(mActivityWindowInfo).isEmbedded(); + + assertTrue(mSplitController.isActivityEmbedded(mActivity)); + } + + @Test + public void testGetEmbeddedActivityWindowInfo() { + mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + final boolean isEmbedded = true; + final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration + .getBounds(); + final Rect taskBounds = new Rect(0, 0, 1000, 2000); + final Rect activityStackBounds = new Rect(0, 0, 500, 2000); + doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded(); + doReturn(taskBounds).when(mActivityWindowInfo).getTaskBounds(); + doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds(); + + final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity, + isEmbedded, activityBounds, taskBounds, activityStackBounds); + assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity)); + } + + @Test + public void testSetEmbeddedActivityWindowInfoCallback() { + mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + final ClientTransactionListenerController controller = ClientTransactionListenerController + .getInstance(); + spyOn(controller); + doNothing().when(controller).registerActivityWindowInfoChangedListener(any()); + doReturn(mActivityWindowInfoListener).when(mSplitController) + .getActivityWindowInfoListener(); + final Executor executor = Runnable::run; + + // Register to ClientTransactionListenerController + mSplitController.setEmbeddedActivityWindowInfoCallback(executor, + mEmbeddedActivityWindowInfoCallback); + + verify(controller).registerActivityWindowInfoChangedListener(mActivityWindowInfoListener); + verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any()); + + // Test onActivityWindowInfoChanged triggered. + mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(), + mActivityWindowInfo); + + verify(mEmbeddedActivityWindowInfoCallback).accept(any()); + + // Unregister to ClientTransactionListenerController + mSplitController.clearEmbeddedActivityWindowInfoCallback(); + + verify(controller).unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); + + // Test onActivityWindowInfoChanged triggered as no-op after clear callback. + clearInvocations(mEmbeddedActivityWindowInfoCallback); + mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(), + mActivityWindowInfo); + + verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any()); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { return createMockActivity(TASK_ID); @@ -1537,13 +1623,17 @@ public class SplitControllerTest { /** Creates a mock activity in the organizer process. */ private Activity createMockActivity(int taskId) { final Activity activity = mock(Activity.class); + final ActivityThread.ActivityClientRecord activityClientRecord = + mock(ActivityThread.ActivityClientRecord.class); doReturn(mActivityResources).when(activity).getResources(); final IBinder activityToken = new Binder(); doReturn(activityToken).when(activity).getActivityToken(); doReturn(activity).when(mSplitController).getActivity(activityToken); + doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity); doReturn(taskId).when(activity).getTaskId(); doReturn(new ActivityInfo()).when(activity).getActivityInfo(); doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); + doReturn(mActivityWindowInfo).when(activityClientRecord).getActivityWindowInfo(); return activity; } diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 8baaf2f155af..a541c590575f 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -145,4 +145,7 @@ <!-- Whether CompatUIController is enabled --> <bool name="config_enableCompatUIController">true</bool> + + <!-- Whether pointer pilfer is required to start back animation. --> + <bool name="config_backAnimationRequiresPointerPilfer">true</bool> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 2606fb661e80..9bd8531d33dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -64,6 +64,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.LatencyTracker; import com.android.internal.view.AppearanceRegion; +import com.android.wm.shell.R; import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; @@ -115,6 +116,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private boolean mShouldStartOnNextMoveEvent = false; private boolean mOnBackStartDispatched = false; private boolean mPointerPilfered = false; + private final boolean mRequirePointerPilfer; private final FlingAnimationUtils mFlingAnimationUtils; @@ -220,6 +222,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mActivityTaskManager = activityTaskManager; mContext = context; mContentResolver = contentResolver; + mRequirePointerPilfer = + context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer); mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); mAnimationBackground = backAnimationBackground; @@ -560,7 +564,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void tryDispatchOnBackStarted( IOnBackInvokedCallback callback, BackMotionEvent backEvent) { - if (mOnBackStartDispatched || callback == null || !mPointerPilfered) { + if (mOnBackStartDispatched + || callback == null + || (!mPointerPilfered && mRequirePointerPilfer)) { return; } dispatchOnBackStarted(callback, backEvent); @@ -1006,6 +1012,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted); pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress); pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent); + pw.println(prefix + " mPointerPilfered=" + mPointerPilfered); + pw.println(prefix + " mRequirePointerPilfer=" + mRequirePointerPilfer); pw.println(prefix + " mCurrentTracker state:"); mCurrentTracker.dump(pw, prefix + " "); pw.println(prefix + " mQueuedTracker state:"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 1d3555cbd309..f4ccd689f938 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -133,8 +133,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final ExclusionRegionListener mExclusionRegionListener = new ExclusionRegionListenerImpl(); - private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId = - new SparseArray<>(); + private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId; private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); private final InputMonitorFactory mInputMonitorFactory; private TaskOperations mTaskOperations; @@ -201,7 +200,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), SurfaceControl.Transaction::new, - rootTaskDisplayAreaOrganizer); + rootTaskDisplayAreaOrganizer, + new SparseArray<>()); } @VisibleForTesting @@ -223,7 +223,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -243,6 +244,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTransactionFactory = transactionFactory; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mInputManager = mContext.getSystemService(InputManager.class); + mWindowDecorByTaskId = windowDecorByTaskId; shellInit.addInitCallback(this::onInit, this); } @@ -344,8 +346,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void destroyWindowDecoration(RunningTaskInfo taskInfo) { - final DesktopModeWindowDecoration decoration = - mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId); + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (decoration == null) return; decoration.close(); @@ -353,6 +354,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (mEventReceiversByDisplay.contains(displayId)) { removeTaskFromEventReceiver(displayId); } + // Remove the decoration from the cache last because WindowDecoration#close could still + // issue CANCEL MotionEvents to touch listeners before its view host is released. + // See b/327664694. + mWindowDecorByTaskId.remove(taskInfo.taskId); } private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 917fd715f71f..9bb5482de715 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -29,6 +29,7 @@ import android.hardware.display.VirtualDisplay import android.os.Handler import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.util.SparseArray import android.view.Choreographer import android.view.Display.DEFAULT_DISPLAY import android.view.IWindowManager @@ -72,6 +73,8 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.whenever import java.util.Optional import java.util.function.Supplier +import org.mockito.Mockito +import org.mockito.kotlin.spy /** Tests of [DesktopModeWindowDecorViewModel] */ @@ -102,6 +105,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { private val transactionFactory = Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() } + private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>()) private lateinit var shellInit: ShellInit private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener @@ -110,6 +114,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Before fun setUp() { shellInit = ShellInit(mockShellExecutor) + windowDecorByTaskIdSpy.clear() desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel( mContext, mockShellExecutor, @@ -128,7 +133,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockDesktopModeWindowDecorFactory, mockInputMonitorFactory, transactionFactory, - mockRootTaskDisplayAreaOrganizer + mockRootTaskDisplayAreaOrganizer, + windowDecorByTaskIdSpy ) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) @@ -332,6 +338,19 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decoration, times(1)).relayout(task) } + @Test + fun testDestroyWindowDecoration_closesBeforeCleanup() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) + val decoration = setUpMockDecorationForTask(task) + val inOrder = Mockito.inOrder(decoration, windowDecorByTaskIdSpy) + + onTaskOpening(task) + desktopModeWindowDecorViewModel.destroyWindowDecoration(task) + + inOrder.verify(decoration).close() + inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId) + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt index 65b73caeada7..0117ece888c0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt @@ -46,7 +46,7 @@ open class WifiUtils { /** * Wrapper the [.getInternetIconResource] for testing compatibility. */ - class InternetIconInjector(protected val context: Context) { + open class InternetIconInjector(protected val context: Context) { /** * Returns the Internet icon for a given RSSI level. * diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java index 085fc2959442..88181e7e2a52 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java @@ -56,10 +56,12 @@ public class AccessibilityMenuService extends AccessibilityService implements View.OnTouchListener { public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName(); + public static final String PACKAGE_TESTS = ".tests"; public static final String INTENT_TOGGLE_MENU = ".toggle_menu"; public static final String INTENT_HIDE_MENU = ".hide_menu"; public static final String INTENT_GLOBAL_ACTION = ".global_action"; public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION"; + public static final String INTENT_OPEN_BLOCKED = "OPEN_BLOCKED"; private static final String TAG = "A11yMenuService"; private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L; @@ -192,7 +194,7 @@ public class AccessibilityMenuService extends AccessibilityService IntentFilter hideMenuFilter = new IntentFilter(); hideMenuFilter.addAction(Intent.ACTION_SCREEN_OFF); - hideMenuFilter.addAction(PACKAGE_NAME + INTENT_HIDE_MENU); + hideMenuFilter.addAction(INTENT_HIDE_MENU); // Including WRITE_SECURE_SETTINGS enforces that we only listen to apps // with the restricted WRITE_SECURE_SETTINGS permission who broadcast this intent. @@ -200,7 +202,7 @@ public class AccessibilityMenuService extends AccessibilityService Manifest.permission.WRITE_SECURE_SETTINGS, null, Context.RECEIVER_EXPORTED); registerReceiver(mToggleMenuReceiver, - new IntentFilter(PACKAGE_NAME + INTENT_TOGGLE_MENU), + new IntentFilter(INTENT_TOGGLE_MENU), Manifest.permission.WRITE_SECURE_SETTINGS, null, Context.RECEIVER_EXPORTED); @@ -245,8 +247,9 @@ public class AccessibilityMenuService extends AccessibilityService * @return {@code true} if successful, {@code false} otherwise. */ private boolean performGlobalActionInternal(int globalAction) { - Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION); + Intent intent = new Intent(INTENT_GLOBAL_ACTION); intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction); + intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS); sendBroadcast(intent); Log.i("A11yMenuService", "Broadcasting global action " + globalAction); return performGlobalAction(globalAction); @@ -410,9 +413,16 @@ public class AccessibilityMenuService extends AccessibilityService private void toggleVisibility() { boolean locked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); - if (!locked && SystemClock.uptimeMillis() - mLastTimeTouchedOutside - > BUTTON_CLICK_TIMEOUT) { - mA11yMenuLayout.toggleVisibility(); + if (!locked) { + if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside + > BUTTON_CLICK_TIMEOUT) { + mA11yMenuLayout.toggleVisibility(); + } + } else { + // Broadcast for testing. + Intent intent = new Intent(INTENT_OPEN_BLOCKED); + intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS); + sendBroadcast(intent); } } } diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index 72c1092b92d6..6546b87c8802 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -23,6 +23,7 @@ import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_QU import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS; import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; +import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_OPEN_BLOCKED; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU; @@ -65,6 +66,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @RunWith(AndroidJUnit4.class) @@ -75,12 +77,11 @@ public class AccessibilityMenuServiceTest { private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5; private static final int TIMEOUT_UI_CHANGE_S = 5; private static final int NO_GLOBAL_ACTION = -1; - private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK"; - private static final String TEST_PIN = "1234"; private static Instrumentation sInstrumentation; private static UiAutomation sUiAutomation; - private static AtomicInteger sLastGlobalAction; + private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION); + private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false); private static AccessibilityManager sAccessibilityManager; private static PowerManager sPowerManager; @@ -122,8 +123,6 @@ public class AccessibilityMenuServiceTest { () -> sAccessibilityManager.getEnabledAccessibilityServiceList( AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter( info -> info.getId().contains(serviceName)).count() == 1); - - sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION); context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -131,20 +130,28 @@ public class AccessibilityMenuServiceTest { sLastGlobalAction.set( intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION)); }}, - new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION), + new IntentFilter(INTENT_GLOBAL_ACTION), + null, null, Context.RECEIVER_EXPORTED); + + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, "Received notification that menu cannot be opened."); + sOpenBlocked.set(true); + }}, + new IntentFilter(INTENT_OPEN_BLOCKED), null, null, Context.RECEIVER_EXPORTED); } @AfterClass - public static void classTeardown() throws Throwable { - clearPin(); + public static void classTeardown() { Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ""); } @Before public void setup() throws Throwable { - clearPin(); + sOpenBlocked.set(false); wakeUpScreen(); sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU"); openMenu(); @@ -154,20 +161,8 @@ public class AccessibilityMenuServiceTest { public void tearDown() throws Throwable { closeMenu(); sLastGlobalAction.set(NO_GLOBAL_ACTION); - } - - private static void clearPin() throws Throwable { - sUiAutomation.executeShellCommand("locksettings clear --old " + TEST_PIN); - TestUtils.waitUntil("Device did not register as unlocked & insecure.", - TIMEOUT_SERVICE_STATUS_CHANGE_S, - () -> !sKeyguardManager.isDeviceSecure()); - } - - private static void setPin() throws Throwable { - sUiAutomation.executeShellCommand("locksettings set-pin " + TEST_PIN); - TestUtils.waitUntil("Device did not recognize as locked & secure.", - TIMEOUT_SERVICE_STATUS_CHANGE_S, - () -> sKeyguardManager.isDeviceSecure()); + // dismisses screenshot popup if present. + sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK"); } private static boolean isMenuVisible() { @@ -184,7 +179,6 @@ public class AccessibilityMenuServiceTest { private static void closeScreen() throws Throwable { Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); - setPin(); sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN); TestUtils.waitUntil("Screen did not close.", TIMEOUT_UI_CHANGE_S, @@ -194,12 +188,20 @@ public class AccessibilityMenuServiceTest { } private static void openMenu() throws Throwable { - Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU); + openMenu(false); + } + + private static void openMenu(boolean abandonOnBlock) throws Throwable { + Intent intent = new Intent(INTENT_TOGGLE_MENU); + intent.setPackage(PACKAGE_NAME); sInstrumentation.getContext().sendBroadcast(intent); TestUtils.waitUntil("Timed out before menu could appear.", TIMEOUT_UI_CHANGE_S, () -> { + if (sOpenBlocked.get() && abandonOnBlock) { + throw new IllegalStateException(); + } if (isMenuVisible()) { return true; } else { @@ -213,7 +215,8 @@ public class AccessibilityMenuServiceTest { if (!isMenuVisible()) { return; } - Intent intent = new Intent(PACKAGE_NAME + INTENT_HIDE_MENU); + Intent intent = new Intent(INTENT_HIDE_MENU); + intent.setPackage(PACKAGE_NAME); sInstrumentation.getContext().sendBroadcast(intent); TestUtils.waitUntil("Timed out before menu could close.", TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible()); @@ -444,13 +447,13 @@ public class AccessibilityMenuServiceTest { closeScreen(); wakeUpScreen(); - boolean timedOut = false; + boolean blocked = false; try { - openMenu(); - } catch (AssertionError e) { + openMenu(true); + } catch (IllegalStateException e) { // Expected - timedOut = true; + blocked = true; } - assertThat(timedOut).isTrue(); + assertThat(blocked).isTrue(); } } diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig index d0e6b2865891..7bbe82c212e7 100644 --- a/packages/SystemUI/aconfig/predictive_back.aconfig +++ b/packages/SystemUI/aconfig/predictive_back.aconfig @@ -4,26 +4,26 @@ flag { name: "predictive_back_sysui" namespace: "systemui" description: "Predictive Back Dispatching for SysUI" - bug: "309545085" + bug: "327737297" } flag { name: "predictive_back_animate_shade" namespace: "systemui" description: "Enable Shade Animations" - bug: "309545085" + bug: "327732946" } flag { name: "predictive_back_animate_bouncer" namespace: "systemui" description: "Enable Predictive Back Animation in Bouncer" - bug: "309545085" + bug: "327733487" } flag { name: "predictive_back_animate_dialogs" namespace: "systemui" description: "Enable Predictive Back Animation for SysUI dialogs" - bug: "309545085" + bug: "327721544" }
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 19b80da62dc7..128b46533a8b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -25,6 +25,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository +import com.android.systemui.common.shared.model.Position import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback @@ -151,6 +152,24 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun clockPosition() = + testScope.runTest { + assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0)) + + underTest.setClockPosition(0, 1) + assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1)) + + underTest.setClockPosition(1, 9) + assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9)) + + underTest.setClockPosition(1, 0) + assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0)) + + underTest.setClockPosition(3, 1) + assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1)) + } + + @Test fun dozeTimeTick() = testScope.runTest { val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 225b5b1408e3..b0f59fe68f11 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -71,7 +71,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) MockitoAnnotations.initMocks(this) - whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) + whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) kosmos.burnInInteractor = burnInInteractor whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) .thenReturn(emptyFlow()) diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml index 68c8dd96d188..d8d298573d04 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf.xml @@ -19,11 +19,11 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/half_shelf_dialog" android:orientation="vertical" - android:layout_width="wrap_content" + android:layout_width="@dimen/large_dialog_width" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" - android:paddingStart="4dp" - android:paddingEnd="4dp"> + android:paddingLeft="@dimen/dialog_side_padding" + android:paddingRight="@dimen/dialog_side_padding"> <LinearLayout android:id="@+id/half_shelf" diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 035cfdc492d0..71ae0d716429 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -223,7 +223,6 @@ <item type="id" name="lock_icon" /> <item type="id" name="lock_icon_bg" /> <item type="id" name="burn_in_layer" /> - <item type="id" name="burn_in_layer_empty_view" /> <item type="id" name="communal_tutorial_indicator" /> <item type="id" name="nssl_placeholder_barrier_bottom" /> <item type="id" name="ambient_indication_container" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index c0ae4a1f4036..9421f150a38a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -53,6 +53,9 @@ import com.android.systemui.Dumpable; import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.clocks.ClockController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.ScreenPowerState; @@ -101,6 +104,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final Rect mClipBounds = new Rect(); private final KeyguardInteractor mKeyguardInteractor; private final PowerInteractor mPowerInteractor; + private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final DozeParameters mDozeParameters; private View mStatusArea = null; @@ -108,6 +112,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private Boolean mSplitShadeEnabled = false; private Boolean mStatusViewCentered = true; + private boolean mGoneToAodTransitionRunning = false; private DumpManager mDumpManager; private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = @@ -176,6 +181,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV KeyguardLogger logger, InteractionJankMonitor interactionJankMonitor, KeyguardInteractor keyguardInteractor, + KeyguardTransitionInteractor keyguardTransitionInteractor, DumpManager dumpManager, PowerInteractor powerInteractor) { super(keyguardStatusView); @@ -191,6 +197,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mDumpManager = dumpManager; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; + mKeyguardTransitionInteractor = keyguardTransitionInteractor; } @Override @@ -225,7 +232,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mDumpManager.registerDumpable(getInstanceName(), this); if (migrateClocksToBlueprint()) { startCoroutines(EmptyCoroutineContext.INSTANCE); - mView.setVisibility(View.GONE); } } @@ -241,6 +247,15 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV dozeTimeTick(); } }, context); + + collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(), + (TransitionStep step) -> { + if (step.getTransitionState() == TransitionState.RUNNING) { + mGoneToAodTransitionRunning = true; + } else { + mGoneToAodTransitionRunning = false; + } + }, context); } public KeyguardStatusView getView() { @@ -311,7 +326,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Set keyguard status view alpha. */ public void setAlpha(float alpha) { - if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) { mView.setAlpha(alpha); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 77054bd29147..2000028dff41 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -88,10 +88,6 @@ public class KeyguardVisibilityHelper { boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState) { - if (migrateClocksToBlueprint()) { - log("Ignoring all of KeyguardVisibilityelper"); - return; - } Assert.isMainThread(); PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA); boolean isOccluded = mKeyguardStateController.isOccluded(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index ad0c3fb0c53a..64e28700aa74 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -24,6 +24,7 @@ import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -79,6 +80,12 @@ interface KeyguardRepository { val keyguardAlpha: StateFlow<Float> /** + * Observable of the relative offset of the lock-screen clock from its natural position on the + * screen. + */ + val clockPosition: StateFlow<Position> + + /** * Observable for whether the keyguard is showing. * * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in @@ -233,6 +240,11 @@ interface KeyguardRepository { fun setKeyguardAlpha(alpha: Float) /** + * Sets the relative offset of the lock-screen clock from its natural position on the screen. + */ + fun setClockPosition(x: Int, y: Int) + + /** * Returns whether the keyguard bottom area should be constrained to the top of the lock icon */ fun isUdfpsSupported(): Boolean @@ -311,6 +323,9 @@ constructor( private val _keyguardAlpha = MutableStateFlow(1f) override val keyguardAlpha = _keyguardAlpha.asStateFlow() + private val _clockPosition = MutableStateFlow(Position(0, 0)) + override val clockPosition = _clockPosition.asStateFlow() + private val _clockShouldBeCentered = MutableStateFlow(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow() @@ -662,6 +677,10 @@ constructor( _keyguardAlpha.value = alpha } + override fun setClockPosition(x: Int, y: Int) { + _clockPosition.value = Position(x, y) + } + override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported override fun setQuickSettingsVisible(isVisible: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index ee892a800b63..7ae70a9a3e7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt @@ -63,19 +63,18 @@ constructor( burnInHelperWrapper.burnInProgressOffset() ) - /** Given the max x,y dimens, determine the current translation shifts. */ - fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> { - return combine( - burnInOffset(xDimenResourceId, isXAxis = true), - burnInOffset(yDimenResourceId, isXAxis = false).map { - it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId) + val keyguardBurnIn: Flow<BurnInModel> = + combine( + burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true), + burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map { + it * 2 - + context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y) } ) { translationX, translationY -> BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale()) } .distinctUntilChanged() .stateIn(scope, SharingStarted.Lazily, BurnInModel()) - } /** * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt index e5bb5a081b70..d2a7486eed0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt @@ -22,8 +22,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow /** Encapsulates business-logic specifically related to the keyguard bottom area. */ @SysUISingleton @@ -37,12 +35,10 @@ constructor( /** The amount of alpha for the UI components of the bottom area. */ val alpha: Flow<Float> = repository.bottomAreaAlpha /** The position of the keyguard clock. */ - private val _clockPosition = MutableStateFlow(Position(0, 0)) - @Deprecated("with migrateClocksToBlueprint()") - val clockPosition: Flow<Position> = _clockPosition.asStateFlow() + val clockPosition: Flow<Position> = repository.clockPosition fun setClockPosition(x: Int, y: Int) { - _clockPosition.value = Position(x, y) + repository.setClockPosition(x, y) } fun setAlpha(alpha: Float) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index fbf1e22ad3e1..5410b10a4b93 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -27,6 +27,7 @@ import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.common.shared.model.Position import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -231,6 +232,9 @@ constructor( /** The approximate location on the screen of the face unlock sensor, if one is available. */ val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation + /** The position of the keyguard clock. */ + val clockPosition: Flow<Position> = repository.clockPosition + @Deprecated("Use the relevant TransitionViewModel") val keyguardAlpha: Flow<Float> = repository.keyguardAlpha @@ -338,6 +342,10 @@ constructor( repository.setQuickSettingsVisible(isVisible) } + fun setClockPosition(x: Int, y: Int) { + repository.setClockPosition(x, y) + } + fun setAlpha(alpha: Float) { repository.setKeyguardAlpha(alpha) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 462d5e6568b0..4812e03ec3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -42,7 +42,7 @@ import kotlin.math.max import kotlinx.coroutines.launch private const val TAG = "KeyguardBlueprintViewBinder" -private const val DEBUG = true +private const val DEBUG = false @SysUISingleton class KeyguardBlueprintViewBinder diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index e67a3245bc85..98bebd091f1a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -21,8 +21,6 @@ import android.content.Context import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView @@ -39,18 +37,13 @@ constructor( private val clockViewModel: KeyguardClockViewModel, ) : KeyguardSection() { private lateinit var burnInLayer: AodBurnInLayer - private lateinit var emptyView: View override fun addViews(constraintLayout: ConstraintLayout) { if (!migrateClocksToBlueprint()) { return } // The burn-in layer requires at least 1 view at all times - emptyView = - View(context, null).apply { - id = R.id.burn_in_layer_empty_view - visibility = View.GONE - } + val emptyView = View(context, null).apply { id = View.generateViewId() } constraintLayout.addView(emptyView) burnInLayer = AodBurnInLayer(context).apply { @@ -77,13 +70,6 @@ constructor( if (!migrateClocksToBlueprint()) { return } - - constraintSet.apply { - // The empty view should not occupy any space - constrainHeight(R.id.burn_in_layer_empty_view, 1) - constrainWidth(R.id.burn_in_layer_empty_view, 0) - connect(R.id.burn_in_layer_empty_view, BOTTOM, PARENT_ID, BOTTOM) - } } override fun removeViews(constraintLayout: ConstraintLayout) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 4ddd039edbaa..6184c82cbff7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -199,7 +199,7 @@ class ClockSizeTransition( private val TRANSITION_PROPERTIES = arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS) - private val DEBUG = true + private val DEBUG = false private val TAG = VisibilityBoundsTransition::class.simpleName!! } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index 62fc1da5a444..7be390a4526f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.util.Log import android.util.MathUtils import com.android.app.animation.Interpolators import com.android.keyguard.KeyguardClockSwitch @@ -63,8 +62,6 @@ constructor( private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, private val keyguardClockViewModel: KeyguardClockViewModel, ) { - private val TAG = "AodBurnInViewModel" - /** Horizontal translation for elements that need to apply anti-burn-in tactics. */ fun translationX( params: BurnInParameters, @@ -74,17 +71,8 @@ constructor( /** Vertical translation for elements that need to apply anti-burn-in tactics. */ fun translationY( - burnInParams: BurnInParameters, + params: BurnInParameters, ): Flow<Float> { - val params = - if (burnInParams.minViewY < burnInParams.topInset) { - // minViewY should never be below the inset. Correct it if needed - Log.w(TAG, "minViewY is below topInset: $burnInParams") - burnInParams.copy(minViewY = burnInParams.topInset) - } else { - burnInParams - } - return configurationInteractor .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y) .flatMapLatest { enterFromTopAmount -> @@ -137,10 +125,7 @@ constructor( keyguardTransitionInteractor.dozeAmountTransition.map { Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) }, - burnInInteractor.burnIn( - xDimenResourceId = R.dimen.burn_in_prevention_offset_x, - yDimenResourceId = R.dimen.burn_in_prevention_offset_y - ) + burnInInteractor.keyguardBurnIn, ) { interpolated, burnIn -> val useScaleOnly = (clockController(params.clockControllerProvider) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt index e35e06533f8c..6458edaecd9e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt @@ -17,14 +17,10 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.Flags.keyguardBottomAreaRefactor -import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.shared.model.BurnInModel -import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -36,10 +32,9 @@ class KeyguardIndicationAreaViewModel @Inject constructor( private val keyguardInteractor: KeyguardInteractor, - private val bottomAreaInteractor: KeyguardBottomAreaInteractor, + bottomAreaInteractor: KeyguardBottomAreaInteractor, keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel, private val burnInHelperWrapper: BurnInHelperWrapper, - private val burnInInteractor: BurnInInteractor, private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, configurationInteractor: ConfigurationInteractor, ) { @@ -68,37 +63,24 @@ constructor( } .distinctUntilChanged() } - - private val burnIn: Flow<BurnInModel> = - burnInInteractor - .burnIn( - xDimenResourceId = R.dimen.burn_in_prevention_offset_x, - yDimenResourceId = R.dimen.default_burn_in_prevention_offset, - ) - .distinctUntilChanged() - /** An observable for the x-offset by which the indication area should be translated. */ val indicationAreaTranslationX: Flow<Float> = - if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) { - burnIn.map { it.translationX.toFloat() } + if (keyguardBottomAreaRefactor()) { + keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() } else { bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() } /** Returns an observable for the y-offset by which the indication area should be translated. */ fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { - return if (migrateClocksToBlueprint()) { - burnIn.map { it.translationY.toFloat() } - } else { - keyguardInteractor.dozeAmount - .map { dozeAmount -> - dozeAmount * - (burnInHelperWrapper.burnInOffset( - /* amplitude = */ defaultBurnInOffset * 2, - /* xAxis= */ false, - ) - defaultBurnInOffset) - } - .distinctUntilChanged() - } + return keyguardInteractor.dozeAmount + .map { dozeAmount -> + dozeAmount * + (burnInHelperWrapper.burnInOffset( + /* amplitude = */ defaultBurnInOffset * 2, + /* xAxis= */ false, + ) - defaultBurnInOffset) + } + .distinctUntilChanged() } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 306aad131624..75bf131afdf9 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -156,6 +156,7 @@ constructor( * desired scene. Once enough of the transition has occurred, the [currentScene] will become * [toScene] (unless the transition is canceled by user action or another call to this method). */ + @JvmOverloads fun changeScene( toScene: SceneKey, loggingReason: String, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index cf7c3e019818..1566de58ef3a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1288,9 +1288,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mView.getContext().getDisplay()); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + } - mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); - mKeyguardStatusViewController.getView().addOnLayoutChangeListener( + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); + mKeyguardStatusViewController.getView().addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { int oldHeight = oldBottom - oldTop; if (v.getHeight() != oldHeight) { @@ -1298,8 +1299,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } }); - updateClockAppearance(); - } + updateClockAppearance(); } @Override @@ -1326,9 +1326,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onSplitShadeEnabledChanged() { mShadeLog.logSplitShadeChanged(mSplitShadeEnabled); - if (!migrateClocksToBlueprint()) { - mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); - } + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); @@ -1443,13 +1441,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(), mStatusBarStateController.getInterpolatedDozeAmount()); - if (!migrateClocksToBlueprint()) { - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - mBarState, - false, - false, - mBarState); - } + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( + mBarState, + false, + false, + mBarState); if (mKeyguardQsUserSwitchController != null) { mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility( mBarState, @@ -1669,11 +1665,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.setLockscreenClockY( mClockPositionAlgorithm.getExpandedPreferredClockY()); } - if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) { + if (keyguardBottomAreaRefactor()) { + mKeyguardInteractor.setClockPosition( + mClockPositionResult.clockX, mClockPositionResult.clockY); + } else { mKeyguardBottomAreaInteractor.setClockPosition( mClockPositionResult.clockX, mClockPositionResult.clockY); } - boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; @@ -1751,11 +1749,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateKeyguardStatusViewAlignment(boolean animate) { + boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); + ConstraintLayout layout; if (migrateClocksToBlueprint()) { - return; + layout = mKeyguardViewConfigurator.getKeyguardRootView(); + } else { + layout = mNotificationContainerParent; } - boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); - ConstraintLayout layout = mNotificationContainerParent; mKeyguardStatusViewController.updateAlignment( layout, mSplitShadeEnabled, shouldBeCentered, animate); mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered)); @@ -3316,9 +3316,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Updates the views to the initial state for the fold to AOD animation. */ @Override public void prepareFoldToAodAnimation() { - if (migrateClocksToBlueprint()) { - return; - } // Force show AOD UI even if we are not locked showAodUi(); @@ -3340,9 +3337,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void startFoldToAodAnimation(Runnable startAction, Runnable endAction, Runnable cancelAction) { - if (migrateClocksToBlueprint()) { - return; - } final ViewPropertyAnimator viewAnimator = mView.animate(); viewAnimator.cancel(); viewAnimator @@ -3378,9 +3372,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Cancels fold to AOD transition and resets view state. */ @Override public void cancelFoldToAodAnimation() { - if (migrateClocksToBlueprint()) { - return; - } cancelAnimation(); resetAlpha(); resetTranslation(); @@ -4455,13 +4446,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } - if (!migrateClocksToBlueprint()) { - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - statusBarState, - keyguardFadingAway, - goingToFullShade, - mBarState); - } + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( + statusBarState, + keyguardFadingAway, + goingToFullShade, + mBarState); if (!keyguardBottomAreaRefactor()) { setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index d1055c77ab8f..ee844345d2ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -81,6 +81,9 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionListener; @@ -157,6 +160,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final AlternateBouncerInteractor mAlternateBouncerInteractor; private final BouncerView mPrimaryBouncerView; private final Lazy<ShadeController> mShadeController; + private final Lazy<SceneInteractor> mSceneInteractorLazy; // Local cache of expansion events, to avoid duplicates private float mFraction = -1f; @@ -381,7 +385,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy, SelectedUserInteractor selectedUserInteractor, Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor, - JavaAdapter javaAdapter + JavaAdapter javaAdapter, + Lazy<SceneInteractor> sceneInteractorLazy ) { mContext = context; mViewMediatorCallback = callback; @@ -415,6 +420,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mSelectedUserInteractor = selectedUserInteractor; mSurfaceBehindInteractor = surfaceBehindInteractor; mJavaAdapter = javaAdapter; + mSceneInteractorLazy = sceneInteractorLazy; } KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -633,8 +639,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void show(Bundle options) { Trace.beginSection("StatusBarKeyguardViewManager#show"); mNotificationShadeWindowController.setKeyguardShowing(true); - mKeyguardStateController.notifyKeyguardState(true, - mKeyguardStateController.isOccluded()); + if (SceneContainerFlag.isEnabled()) { + mSceneInteractorLazy.get().changeScene( + Scenes.Lockscreen, "StatusBarKeyguardViewManager.show"); + } + mKeyguardStateController.notifyKeyguardState(true, mKeyguardStateController.isOccluded()); reset(true /* hideBouncerWhenShowing */); SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt index d19a3364d502..5c53ff98b777 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt @@ -16,8 +16,6 @@ package com.android.systemui.unfold -import android.animation.Animator -import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.annotation.BinderThread import android.content.Context @@ -25,6 +23,7 @@ import android.os.Handler import android.os.SystemProperties import android.util.Log import android.view.animation.DecelerateInterpolator +import androidx.core.animation.addListener import com.android.internal.foldables.FoldLockSettingAvailabilityProvider import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository @@ -37,25 +36,17 @@ import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Comp import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.util.animation.data.repository.AnimationStatusRepository import javax.inject.Inject -import kotlin.coroutines.resume import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.android.asCoroutineDispatcher -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout -@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class FoldLightRevealOverlayAnimation @Inject constructor( @@ -70,9 +61,6 @@ constructor( private val revealProgressValueAnimator: ValueAnimator = ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT) - private val areAnimationEnabled: Flow<Boolean> - get() = animationStatusRepository.areAnimationsEnabled() - private lateinit var controller: FullscreenLightRevealAnimationController @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null @@ -101,30 +89,33 @@ constructor( applicationScope.launch(bgHandler.asCoroutineDispatcher()) { deviceStateRepository.state - .map { it == DeviceStateRepository.DeviceState.FOLDED } + .map { it != DeviceStateRepository.DeviceState.FOLDED } .distinctUntilChanged() - .flatMapLatest { isFolded -> - flow<Nothing> { - if (!areAnimationEnabled.first() || !isFolded) { - return@flow - } - withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) { - readyCallback = CompletableDeferred() - val onReady = readyCallback?.await() - readyCallback = null - controller.addOverlay(ALPHA_OPAQUE, onReady) - waitForScreenTurnedOn() - } - playFoldLightRevealOverlayAnimation() - } - .catchTimeoutAndLog() - .onCompletion { - val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted() - onReady?.run() + .filter { isUnfolded -> isUnfolded } + .collect { controller.ensureOverlayRemoved() } + } + + applicationScope.launch(bgHandler.asCoroutineDispatcher()) { + deviceStateRepository.state + .filter { + animationStatusRepository.areAnimationsEnabled().first() && + it == DeviceStateRepository.DeviceState.FOLDED + } + .collect { + try { + withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) { + readyCallback = CompletableDeferred() + val onReady = readyCallback?.await() readyCallback = null + controller.addOverlay(ALPHA_OPAQUE, onReady) + waitForScreenTurnedOn() + playFoldLightRevealOverlayAnimation() } + } catch (e: TimeoutCancellationException) { + Log.e(TAG, "Fold light reveal animation timed out") + ensureOverlayRemovedInternal() + } } - .collect {} } } @@ -137,34 +128,19 @@ constructor( powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first() } - private suspend fun playFoldLightRevealOverlayAnimation() { + private fun ensureOverlayRemovedInternal() { + revealProgressValueAnimator.cancel() + controller.ensureOverlayRemoved() + } + + private fun playFoldLightRevealOverlayAnimation() { revealProgressValueAnimator.duration = ANIMATION_DURATION revealProgressValueAnimator.interpolator = DecelerateInterpolator() revealProgressValueAnimator.addUpdateListener { animation -> controller.updateRevealAmount(animation.animatedFraction) } - revealProgressValueAnimator.startAndAwaitCompletion() - } - - private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit = - suspendCancellableCoroutine { continuation -> - val listener = - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - continuation.resume(Unit) - removeListener(this) - } - } - addListener(listener) - continuation.invokeOnCancellation { removeListener(listener) } - start() - } - - private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception -> - when (exception) { - is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out") - else -> throw exception - } + revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() }) + revealProgressValueAnimator.start() } private companion object { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 90587d7386ce..13fb42ce8c3e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,6 +32,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; @@ -59,6 +62,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardStatusViewController mControllerMock; @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; + @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock protected DumpManager mDumpManager; protected FakeKeyguardRepository mFakeKeyguardRepository; protected FakePowerRepository mFakePowerRepository; @@ -89,6 +93,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mKeyguardLogger, mInteractionJankMonitor, deps.getKeyguardInteractor(), + mKeyguardTransitionInteractor, mDumpManager, PowerInteractorFactory.create( mFakePowerRepository @@ -105,6 +110,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver); when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch); + when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow()); when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area)) .thenReturn(mKeyguardStatusAreaView); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt index d0b1dd52c7e7..df52265384fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt @@ -122,13 +122,7 @@ class BurnInInteractorTest : SysuiTestCase() { testScope.runTest { whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f) - val burnInModel by - collectLastValue( - underTest.burnIn( - xDimenResourceId = R.dimen.burn_in_prevention_offset_x, - yDimenResourceId = R.dimen.burn_in_prevention_offset_y - ) - ) + val burnInModel by collectLastValue(underTest.keyguardBurnIn) // After time tick, returns the configured values fakeKeyguardRepository.dozeTimeTick(10) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt index ce089b1b723a..864acfb1ddb9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase @@ -25,13 +24,9 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition -import com.android.systemui.kosmos.testScope -import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -41,23 +36,18 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(JUnit4::class) class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel - @Mock private lateinit var burnInInteractor: BurnInInteractor - private val burnInFlow = MutableStateFlow(BurnInModel()) - - private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor private lateinit var underTest: KeyguardIndicationAreaViewModel private lateinit var repository: FakeKeyguardRepository @@ -80,11 +70,9 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) whenever(burnInHelperWrapper.burnInOffset(anyInt(), any())) .thenReturn(RETURNED_BURN_IN_OFFSET) - whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) val withDeps = KeyguardInteractorFactory.create() val keyguardInteractor = withDeps.keyguardInteractor @@ -94,85 +82,78 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow) whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow) whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow) - bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository) underTest = KeyguardIndicationAreaViewModel( keyguardInteractor = keyguardInteractor, - bottomAreaInteractor = bottomAreaInteractor, + bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), keyguardBottomAreaViewModel = bottomAreaViewModel, burnInHelperWrapper = burnInHelperWrapper, - burnInInteractor = burnInInteractor, shortcutsCombinedViewModel = shortcutsCombinedViewModel, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), ) } @Test - fun alpha() = - testScope.runTest { - val value = collectLastValue(underTest.alpha) - - assertThat(value()).isEqualTo(1f) - alphaFlow.value = 0.1f - assertThat(value()).isEqualTo(0.1f) - alphaFlow.value = 0.5f - assertThat(value()).isEqualTo(0.5f) - alphaFlow.value = 0.2f - assertThat(value()).isEqualTo(0.2f) - alphaFlow.value = 0f - assertThat(value()).isEqualTo(0f) - } + fun alpha() = runTest { + val value = collectLastValue(underTest.alpha) + + assertThat(value()).isEqualTo(1f) + alphaFlow.value = 0.1f + assertThat(value()).isEqualTo(0.1f) + alphaFlow.value = 0.5f + assertThat(value()).isEqualTo(0.5f) + alphaFlow.value = 0.2f + assertThat(value()).isEqualTo(0.2f) + alphaFlow.value = 0f + assertThat(value()).isEqualTo(0f) + } @Test - fun isIndicationAreaPadded() = - testScope.runTest { - repository.setKeyguardShowing(true) - val value = collectLastValue(underTest.isIndicationAreaPadded) - - assertThat(value()).isFalse() - startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) - assertThat(value()).isTrue() - endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) - assertThat(value()).isTrue() - startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) - assertThat(value()).isTrue() - endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) - assertThat(value()).isFalse() - } + fun isIndicationAreaPadded() = runTest { + repository.setKeyguardShowing(true) + val value = collectLastValue(underTest.isIndicationAreaPadded) + + assertThat(value()).isFalse() + startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) + assertThat(value()).isTrue() + endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) + assertThat(value()).isTrue() + startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) + assertThat(value()).isTrue() + endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) + assertThat(value()).isFalse() + } @Test - fun indicationAreaTranslationX() = - testScope.runTest { - val value = collectLastValue(underTest.indicationAreaTranslationX) - - assertThat(value()).isEqualTo(0f) - bottomAreaInteractor.setClockPosition(100, 100) - assertThat(value()).isEqualTo(100f) - bottomAreaInteractor.setClockPosition(200, 100) - assertThat(value()).isEqualTo(200f) - bottomAreaInteractor.setClockPosition(200, 200) - assertThat(value()).isEqualTo(200f) - bottomAreaInteractor.setClockPosition(300, 100) - assertThat(value()).isEqualTo(300f) - } + fun indicationAreaTranslationX() = runTest { + val value = collectLastValue(underTest.indicationAreaTranslationX) + + assertThat(value()).isEqualTo(0f) + repository.setClockPosition(100, 100) + assertThat(value()).isEqualTo(100f) + repository.setClockPosition(200, 100) + assertThat(value()).isEqualTo(200f) + repository.setClockPosition(200, 200) + assertThat(value()).isEqualTo(200f) + repository.setClockPosition(300, 100) + assertThat(value()).isEqualTo(300f) + } @Test - fun indicationAreaTranslationY() = - testScope.runTest { - val value = - collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) - - // Negative 0 - apparently there's a difference in floating point arithmetic - FML - assertThat(value()).isEqualTo(-0f) - val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) - assertThat(value()).isEqualTo(expected1) - val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) - assertThat(value()).isEqualTo(expected2) - val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) - assertThat(value()).isEqualTo(expected3) - val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) - assertThat(value()).isEqualTo(expected4) - } + fun indicationAreaTranslationY() = runTest { + val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) + + // Negative 0 - apparently there's a difference in floating point arithmetic - FML + assertThat(value()).isEqualTo(-0f) + val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) + assertThat(value()).isEqualTo(expected1) + val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) + assertThat(value()).isEqualTo(expected2) + val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) + assertThat(value()).isEqualTo(expected3) + val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) + assertThat(value()).isEqualTo(expected4) + } private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { repository.setDozeAmount(dozeAmount) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index f8771b26476c..fd7b1399d03f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -461,6 +461,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardLogger, mInteractionJankMonitor, mKeyguardInteractor, + mKeyguardTransitionInteractor, mDumpManager, mPowerInteractor)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 3666248d1783..f050857d5df2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -86,6 +86,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionChangeEvent; @@ -224,7 +225,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> mock(KeyguardDismissActionInteractor.class), mSelectedUserInteractor, () -> mock(KeyguardSurfaceBehindInteractor.class), - mock(JavaAdapter.class)) { + mock(JavaAdapter.class), + () -> mock(SceneInteractor.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -733,7 +735,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> mock(KeyguardDismissActionInteractor.class), mSelectedUserInteractor, () -> mock(KeyguardSurfaceBehindInteractor.class), - mock(JavaAdapter.class)) { + mock(JavaAdapter.class), + () -> mock(SceneInteractor.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 1e305d67d40d..793e2d7efcda 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point +import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource @@ -57,6 +58,9 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _bottomAreaAlpha = MutableStateFlow(1f) override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha + private val _clockPosition = MutableStateFlow(Position(0, 0)) + override val clockPosition: StateFlow<Position> = _clockPosition + private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing @@ -145,6 +149,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _bottomAreaAlpha.value = alpha } + override fun setClockPosition(x: Int, y: Int) { + _clockPosition.value = Position(x, y) + } + fun setKeyguardShowing(isShowing: Boolean) { _isKeyguardShowing.value = isShowing } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index f4ea754c21ba..279bd72da6e7 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -1004,17 +1004,19 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); - } // TODO(b/319537921): should there be an else here? - //Primary pointer is swiping, so transit to PanningScalingState - transitToPanningScalingStateAndClear(); + } else { + //Primary pointer is swiping, so transit to PanningScalingState + transitToPanningScalingStateAndClear(); + } } else if (mIsSinglePanningEnabled && isActivated() && event.getPointerCount() == 1) { if (overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); - } // TODO(b/319537921): should there be an else here? - transitToSinglePanningStateAndClear(); + } else { + transitToSinglePanningStateAndClear(); + } } else if (!mIsTwoFingerCountReached) { // If it is a two-finger gesture, do not transition to the // delegating state to ensure the reachability of @@ -1257,17 +1259,19 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); + } else { + //Primary pointer is swiping, so transit to PanningScalingState + transitToPanningScalingStateAndClear(); } - //Primary pointer is swiping, so transit to PanningScalingState - transitToPanningScalingStateAndClear(); } else if (mIsSinglePanningEnabled && isActivated() && event.getPointerCount() == 1) { if (overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); + } else { + transitToSinglePanningStateAndClear(); } - transitToSinglePanningStateAndClear(); } else { transitionToDelegatingStateAndClear(); } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 6f45f60f6dfa..b932743aab1e 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -2074,6 +2074,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks, int appWidgetId, int viewId, long requestId) { try { + Slog.d(TAG, "Trying to notify widget view data changed"); callbacks.viewDataChanged(appWidgetId, viewId); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2158,6 +2159,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks, int appWidgetId, RemoteViews views, long requestId) { try { + Slog.d(TAG, "Trying to notify widget update for package " + + (views == null ? "null" : views.getPackage()) + + " with widget id: " + appWidgetId); callbacks.updateAppWidget(appWidgetId, views); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2196,6 +2200,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks, int appWidgetId, AppWidgetProviderInfo info, long requestId) { try { + Slog.d(TAG, "Trying to notify provider update"); callbacks.providerChanged(appWidgetId, info); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2239,6 +2244,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyAppWidgetRemoved(Host host, IAppWidgetHost callbacks, int appWidgetId, long requestId) { try { + Slog.d(TAG, "Trying to notify widget removed"); callbacks.appWidgetRemoved(appWidgetId); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2286,6 +2292,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) { try { + Slog.d(TAG, "Trying to notify widget providers changed"); callbacks.providersChanged(); } catch (RemoteException re) { synchronized (mLock) { diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 9189ea763577..e1d7be121865 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -348,6 +348,9 @@ public class SystemConfig { // marked as stopped by the system @NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>(); + // Which packages (key) are allowed to join particular SharedUid (value). + @NonNull private final Map<String, String> mPackageToSharedUidAllowList = new ArrayMap<>(); + // A map of preloaded package names and the path to its app metadata file path. private final ArrayMap<String, String> mAppMetadataFilePaths = new ArrayMap<>(); @@ -567,6 +570,11 @@ public class SystemConfig { return mInitialNonStoppedSystemPackages; } + @NonNull + public Map<String, String> getPackageToSharedUidAllowList() { + return mPackageToSharedUidAllowList; + } + public ArrayMap<String, String> getAppMetadataFilePaths() { return mAppMetadataFilePaths; } @@ -1563,6 +1571,19 @@ public class SystemConfig { mInitialNonStoppedSystemPackages.add(pkgName); } } break; + case "allow-package-shareduid": { + String pkgName = parser.getAttributeValue(null, "package"); + String sharedUid = parser.getAttributeValue(null, "shareduid"); + if (TextUtils.isEmpty(pkgName)) { + Slog.w(TAG, "<" + name + "> without package in " + permFile + + " at " + parser.getPositionDescription()); + } else if (TextUtils.isEmpty(sharedUid)) { + Slog.w(TAG, "<" + name + "> without shareduid in " + permFile + + " at " + parser.getPositionDescription()); + } else { + mPackageToSharedUidAllowList.put(pkgName, sharedUid); + } + } case "asl-file": { String packageName = parser.getAttributeValue(null, "package"); String path = parser.getAttributeValue(null, "path"); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b8e09cce93b9..258f53d982d2 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -372,6 +372,15 @@ public final class ActiveServices { @Overridable public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L; + /** + * Disables foreground service background starts in System Alert Window for all types + * unless it already has a System Overlay Window. + */ + @ChangeId + @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM) + @Overridable + public static final long FGS_SAW_RESTRICTIONS = 319471980L; + final ActivityManagerService mAm; // Maximum number of services that we allow to start in the background @@ -8526,10 +8535,31 @@ public final class ActiveServices { } } + // The flag being enabled isn't enough to deny background start: we need to also check + // if there is a system alert UI present. if (ret == REASON_DENIED) { - if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, - callingPackage)) { - ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + // Flag check: are we disabling SAW FGS background starts? + final boolean shouldDisableSaw = Flags.fgsDisableSaw() + && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid); + if (shouldDisableSaw) { + final ProcessRecord processRecord = mAm + .getProcessRecordLocked(targetService.processName, + targetService.appInfo.uid); + if (processRecord != null) { + if (processRecord.mState.hasOverlayUi()) { + if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, + callingPackage)) { + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + } + } + } else { + Slog.e(TAG, "Could not find process record for SAW check"); + } + } else { + if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, + callingPackage)) { + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + } } } diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig new file mode 100644 index 000000000000..10b5eff06e0c --- /dev/null +++ b/services/core/java/com/android/server/flags/services.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.flags" + +flag { + namespace: "wear_frameworks" + name: "enable_odp_feature_guard" + description: "Enable guard based on system feature to prevent OnDevicePersonalization service from starting on form factors." + bug: "322249125" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index a79e7715f064..05b1cb69235b 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -54,6 +54,7 @@ import android.hardware.input.InputManager; import android.hardware.input.InputSensorInfo; import android.hardware.input.InputSettings; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult; import android.hardware.input.TouchCalibration; import android.hardware.lights.Light; import android.hardware.lights.LightState; @@ -1244,9 +1245,9 @@ public class InputManagerService extends IInputManager.Stub } @Override // Binder call - public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, - @Nullable InputMethodSubtype imeSubtype) { + public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( + InputDeviceIdentifier identifier, @UserIdInt int userId, + @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype); } diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 46668de042d4..283e692ffbab 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -16,10 +16,11 @@ package com.android.server.input; -import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT; -import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE; -import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER; -import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD; +import static android.hardware.input.KeyboardLayoutSelectionResult.FAILED; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT; import android.annotation.AnyThread; import android.annotation.MainThread; @@ -46,6 +47,7 @@ import android.content.res.XmlResourceParser; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult; import android.icu.lang.UScript; import android.icu.util.ULocale; import android.os.Bundle; @@ -79,7 +81,6 @@ import com.android.internal.util.XmlUtils; import com.android.server.LocalServices; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent; -import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria; import com.android.server.inputmethod.InputMethodManagerInternal; import libcore.io.Streams; @@ -130,7 +131,8 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { // This cache stores "best-matched" layouts so that we don't need to run the matching // algorithm repeatedly. @GuardedBy("mKeyboardLayoutCache") - private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>(); + private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache = + new ArrayMap<>(); private final Object mImeInfoLock = new Object(); @Nullable @GuardedBy("mImeInfoLock") @@ -222,17 +224,17 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } else { Set<String> selectedLayouts = new HashSet<>(); List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping(); - List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>(); + List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>(); boolean hasMissingLayout = false; for (ImeInfo imeInfo : imeInfoList) { // Check if the layout has been previously configured - KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal( + KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( keyboardIdentifier, imeInfo); - boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null; + boolean noLayoutFound = result.getLayoutDescriptor() == null; if (!noLayoutFound) { - selectedLayouts.add(layoutInfo.mDescriptor); + selectedLayouts.add(result.getLayoutDescriptor()); } - layoutInfoList.add(layoutInfo); + resultList.add(result); hasMissingLayout |= noLayoutFound; } @@ -260,7 +262,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } if (shouldLogConfiguration) { - logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList, + logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList, !mDataStore.hasInputDeviceEntry(key)); } } finally { @@ -757,10 +759,10 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { String keyboardLayoutDescriptor; if (useNewSettingsUi()) { synchronized (mImeInfoLock) { - KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal( + KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( new KeyboardIdentifier(identifier, languageTag, layoutType), mCurrentImeInfo); - keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor; + keyboardLayoutDescriptor = result.getLayoutDescriptor(); } } else { keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier); @@ -788,26 +790,26 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @AnyThread - @Nullable - public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, - @Nullable InputMethodSubtype imeSubtype) { + @NonNull + public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( + InputDeviceIdentifier identifier, @UserIdInt int userId, + @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { if (!useNewSettingsUi()) { Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported"); - return null; + return FAILED; } InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { - return null; + return FAILED; } KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice); - KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal( + KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( keyboardIdentifier, new ImeInfo(userId, imeInfo, imeSubtype)); if (DEBUG) { Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : " - + userId + ", subtype = " + imeSubtype + " -> " + layoutInfo); + + userId + ", subtype = " + imeSubtype + " -> " + result); } - return layoutInfo != null ? layoutInfo.mDescriptor : null; + return result; } @AnyThread @@ -942,13 +944,13 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @Nullable - private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal( + private KeyboardLayoutSelectionResult getKeyboardLayoutForInputDeviceInternal( KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo) { String layoutKey = new LayoutKey(keyboardIdentifier, imeInfo).toString(); synchronized (mDataStore) { String layout = mDataStore.getKeyboardLayout(keyboardIdentifier.toString(), layoutKey); if (layout != null) { - return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER); + return new KeyboardLayoutSelectionResult(layout, LAYOUT_SELECTION_CRITERIA_USER); } } @@ -961,17 +963,17 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal( keyboardIdentifier, imeInfo); // Call auto-matching algorithm to find the best matching layout - KeyboardLayoutInfo layoutInfo = + KeyboardLayoutSelectionResult result = getDefaultKeyboardLayoutBasedOnImeInfo(keyboardIdentifier, imeInfo, layoutList); - mKeyboardLayoutCache.put(layoutKey, layoutInfo); - return layoutInfo; + mKeyboardLayoutCache.put(layoutKey, result); + return result; } } } - @Nullable - private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo( + @NonNull + private static KeyboardLayoutSelectionResult getDefaultKeyboardLayoutBasedOnImeInfo( KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) { Arrays.sort(layoutList); @@ -986,7 +988,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { + "vendor and product Ids. " + keyboardIdentifier + " : " + layout.getDescriptor()); } - return new KeyboardLayoutInfo(layout.getDescriptor(), + return new KeyboardLayoutSelectionResult(layout.getDescriptor(), LAYOUT_SELECTION_CRITERIA_DEVICE); } } @@ -1004,13 +1006,14 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { + "HW information (Language tag and Layout type). " + keyboardIdentifier + " : " + layoutDesc); } - return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE); + return new KeyboardLayoutSelectionResult(layoutDesc, + LAYOUT_SELECTION_CRITERIA_DEVICE); } } if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) { // Can't auto select layout based on IME info is null - return null; + return FAILED; } InputMethodSubtype subtype = imeInfo.mImeSubtype; @@ -1027,9 +1030,10 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { + layoutDesc); } if (layoutDesc != null) { - return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD); + return new KeyboardLayoutSelectionResult(layoutDesc, + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD); } - return null; + return FAILED; } @Nullable @@ -1246,21 +1250,23 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice, - @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList, + @NonNull List<ImeInfo> imeInfoList, + @NonNull List<KeyboardLayoutSelectionResult> resultList, boolean isFirstConfiguration) { - if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) { + if (imeInfoList.isEmpty() || resultList.isEmpty()) { return; } KeyboardConfigurationEvent.Builder configurationEventBuilder = new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration( isFirstConfiguration); for (int i = 0; i < imeInfoList.size(); i++) { - KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i); + KeyboardLayoutSelectionResult result = resultList.get(i); String layoutName = null; int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT; - if (layoutInfo != null && layoutInfo.mDescriptor != null) { - layoutSelectionCriteria = layoutInfo.mSelectionCriteria; - KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor); + if (result != null && result.getLayoutDescriptor() != null) { + layoutSelectionCriteria = result.getSelectionCriteria(); + KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse( + result.getLayoutDescriptor()); if (d != null) { layoutName = d.keyboardLayoutName; } @@ -1477,33 +1483,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } - private static class KeyboardLayoutInfo { - @Nullable - private final String mDescriptor; - @LayoutSelectionCriteria - private final int mSelectionCriteria; - - private KeyboardLayoutInfo(@Nullable String descriptor, - @LayoutSelectionCriteria int selectionCriteria) { - mDescriptor = descriptor; - mSelectionCriteria = selectionCriteria; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof KeyboardLayoutInfo) { - return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor) - && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria; - } - return false; - } - - @Override - public int hashCode() { - return 31 * mSelectionCriteria + mDescriptor.hashCode(); - } - } - private interface KeyboardLayoutVisitor { void visitKeyboardLayout(Resources resources, int keyboardLayoutResId, KeyboardLayout layout); diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java index f53b9411a6a7..b8ae737919d9 100644 --- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java +++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java @@ -16,14 +16,18 @@ package com.android.server.input; -import static java.lang.annotation.RetentionPolicy.SOURCE; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT; +import static android.hardware.input.KeyboardLayoutSelectionResult.layoutSelectionCriteriaToString; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.role.RoleManager; import android.content.Intent; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria; import android.icu.util.ULocale; import android.text.TextUtils; import android.util.Log; @@ -40,7 +44,6 @@ import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutCon import com.android.internal.util.FrameworkStatsLog; import com.android.server.policy.ModifierShortcutManager; -import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -57,32 +60,6 @@ public final class KeyboardMetricsCollector { // (requires restart) private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - @Retention(SOURCE) - @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = { - LAYOUT_SELECTION_CRITERIA_UNSPECIFIED, - LAYOUT_SELECTION_CRITERIA_USER, - LAYOUT_SELECTION_CRITERIA_DEVICE, - LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, - LAYOUT_SELECTION_CRITERIA_DEFAULT - }) - public @interface LayoutSelectionCriteria { - } - - /** Unspecified layout selection criteria */ - public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0; - - /** Manual selection by user */ - public static final int LAYOUT_SELECTION_CRITERIA_USER = 1; - - /** Auto-detection based on device provided language tag and layout type */ - public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2; - - /** Auto-detection based on IME provided language tag and layout type */ - public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3; - - /** Default selection */ - public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4; - @VisibleForTesting static final String DEFAULT_LAYOUT_NAME = "Default"; @@ -629,30 +606,16 @@ public final class KeyboardMetricsCollector { @Override public String toString() { - return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = " + return "{keyboardLanguageTag = " + keyboardLanguageTag + + " keyboardLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType) - + " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = " - + getStringForSelectionCriteria(layoutSelectionCriteria) - + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = " - + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}"; - } - } - - private static String getStringForSelectionCriteria( - @LayoutSelectionCriteria int layoutSelectionCriteria) { - switch (layoutSelectionCriteria) { - case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED: - return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED"; - case LAYOUT_SELECTION_CRITERIA_USER: - return "LAYOUT_SELECTION_CRITERIA_USER"; - case LAYOUT_SELECTION_CRITERIA_DEVICE: - return "LAYOUT_SELECTION_CRITERIA_DEVICE"; - case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD: - return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD"; - case LAYOUT_SELECTION_CRITERIA_DEFAULT: - return "LAYOUT_SELECTION_CRITERIA_DEFAULT"; - default: - return "INVALID_CRITERIA"; + + " keyboardLayoutName = " + keyboardLayoutName + + " layoutSelectionCriteria = " + + layoutSelectionCriteriaToString(layoutSelectionCriteria) + + " imeLanguageTag = " + imeLanguageTag + + " imeLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue( + imeLayoutType) + + "}"; } } diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index 8826e3d2d345..73f1aad31d72 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -91,10 +91,10 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_SHOW_IME, statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE, - Objects.toString(mService.mCurFocusedWindow), + Objects.toString(mService.mImeBindingState.mFocusedWindow), InputMethodDebug.softInputDisplayReasonToString(reason), InputMethodDebug.softInputModeToString( - mService.mCurFocusedWindowSoftInputMode)); + mService.mImeBindingState.mFocusedWindowSoftInputMode)); } mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason, statsToken); @@ -122,10 +122,10 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_HIDE_IME, statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE, - Objects.toString(mService.mCurFocusedWindow), + Objects.toString(mService.mImeBindingState.mFocusedWindow), InputMethodDebug.softInputDisplayReasonToString(reason), InputMethodDebug.softInputModeToString( - mService.mCurFocusedWindowSoftInputMode)); + mService.mImeBindingState.mFocusedWindowSoftInputMode)); } mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason, statsToken); @@ -207,7 +207,8 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @Override public boolean removeImeScreenshot(int displayId) { if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) { - mService.onShowHideSoftInputRequested(false /* show */, mService.mCurFocusedWindow, + mService.onShowHideSoftInputRequested(false /* show */, + mService.mImeBindingState.mFocusedWindow, REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */); return true; } diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java new file mode 100644 index 000000000000..4c20c3b9784a --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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.inputmethod; + +import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME; +import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; + +import android.annotation.Nullable; +import android.os.IBinder; +import android.util.Printer; +import android.util.proto.ProtoOutputStream; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; +import android.view.inputmethod.EditorInfo; + +import com.android.internal.inputmethod.InputMethodDebug; +import com.android.server.wm.WindowManagerInternal; + +/** + * Stores information related to one active IME client on one display. + */ +final class ImeBindingState { + + /** + * The last window token that we confirmed to be focused. This is always updated upon + * reports from the input method client. If the window state is already changed before the + * report is handled, this field just keeps the last value. + */ + @Nullable + final IBinder mFocusedWindow; + + /** + * {@link WindowManager.LayoutParams#softInputMode} of {@link #mFocusedWindow}. + * + * @see #mFocusedWindow + */ + @SoftInputModeFlags + final int mFocusedWindowSoftInputMode; + + /** + * The client by which {@link #mFocusedWindow} was reported. This gets updated whenever + * an + * IME-focusable window gained focus (without necessarily starting an input connection), + * while {@link InputMethodManagerService#mClient} only gets updated when we actually start an + * input connection. + * + * @see #mFocusedWindow + */ + @Nullable + final ClientState mFocusedWindowClient; + + /** + * The editor info by which {@link #mFocusedWindow} was reported. This differs from + * {@link InputMethodManagerService#mCurEditorInfo} the same way {@link #mFocusedWindowClient} + * differs from {@link InputMethodManagerService#mCurClient}. + * + * @see #mFocusedWindow + */ + @Nullable + final EditorInfo mFocusedWindowEditorInfo; + + void dumpDebug(ProtoOutputStream proto, WindowManagerInternal windowManagerInternal) { + proto.write(CUR_FOCUSED_WINDOW_NAME, + windowManagerInternal.getWindowName(mFocusedWindow)); + proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, + InputMethodDebug.softInputModeToString(mFocusedWindowSoftInputMode)); + } + + void dump(String prefix, Printer p) { + p.println(prefix + "mFocusedWindow()=" + mFocusedWindow); + p.println(prefix + "softInputMode=" + InputMethodDebug.softInputModeToString( + mFocusedWindowSoftInputMode)); + p.println(prefix + "mFocusedWindowClient=" + mFocusedWindowClient); + } + + static ImeBindingState newEmptyState() { + return new ImeBindingState( + /*focusedWindow=*/ null, + /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED, + /*focusedWindowClient=*/ null, + /*focusedWindowEditorInfo=*/ null + ); + } + + ImeBindingState(@Nullable IBinder focusedWindow, + @SoftInputModeFlags int focusedWindowSoftInputMode, + @Nullable ClientState focusedWindowClient, + @Nullable EditorInfo focusedWindowEditorInfo) { + mFocusedWindow = focusedWindow; + mFocusedWindowSoftInputMode = focusedWindowSoftInputMode; + mFocusedWindowClient = focusedWindowClient; + mFocusedWindowEditorInfo = focusedWindowEditorInfo; + } +} diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index 743b8e382347..cdfde87f042f 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -548,7 +548,7 @@ public final class ImeVisibilityStateComputer { } } // Fallback to the focused window for some edge cases (e.g. relaunching the activity) - return mService.mCurFocusedWindow; + return mService.mImeBindingState.mFocusedWindow; } IBinder getWindowTokenFrom(ImeTargetWindowState windowState) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 1564b2f86218..fee0342a9f99 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -28,7 +28,6 @@ import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DIS import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT; -import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID; @@ -423,6 +422,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private final ClientController mClientController; /** + * Holds the current IME binding state info. + */ + ImeBindingState mImeBindingState; + + /** * Set once the system is ready to run third party code. */ boolean mSystemReady; @@ -482,13 +486,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private ClientState mCurClient; /** - * The last window token that we confirmed to be focused. This is always updated upon reports - * from the input method client. If the window state is already changed before the report is - * handled, this field just keeps the last value. - */ - IBinder mCurFocusedWindow; - - /** * The last window token that we confirmed that IME started talking to. This is always updated * upon reports from the input method. If the window state is already changed before the report * is handled, this field just keeps the last value. @@ -496,34 +493,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub IBinder mLastImeTargetWindow; /** - * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}. - * - * @see #mCurFocusedWindow - */ - @SoftInputModeFlags - int mCurFocusedWindowSoftInputMode; - - /** - * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an - * IME-focusable window gained focus (without necessarily starting an input connection), - * while {@link #mCurClient} only gets updated when we actually start an input connection. - * - * @see #mCurFocusedWindow - */ - @Nullable - ClientState mCurFocusedWindowClient; - - /** - * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from - * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs - * from {@link #mCurClient}. - * - * @see #mCurFocusedWindow - */ - @Nullable - EditorInfo mCurFocusedWindowEditorInfo; - - /** * The {@link IRemoteInputConnection} last provided by the current client. */ IRemoteInputConnection mCurInputConnection; @@ -1131,10 +1100,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard( accessibilitySoftKeyboardSetting); if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) { - hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE); } else if (isShowRequestedForCurrentWindow()) { - showCurrentInputLocked(mCurFocusedWindow, InputMethodManager.SHOW_IMPLICIT, + showCurrentInputLocked(mImeBindingState.mFocusedWindow, + InputMethodManager.SHOW_IMPLICIT, SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE); } } else if (stylusHandwritingEnabledUri.equals(uri)) { @@ -1629,7 +1599,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // Hide soft input before user switch task since switch task may block main handler a while // and delayed the hideCurrentInputLocked(). - hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_SWITCH_USER); final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, clientToBeReset); @@ -1704,6 +1674,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mClientController = new ClientController(mPackageManagerInternal); synchronized (ImfLock.class) { mClientController.addClientControllerCallback(c -> onClientRemoved(c)); + mImeBindingState = ImeBindingState.newEmptyState(); } mPreventImeStartupUnlessTextEditor = mRes.getBoolean( @@ -2218,7 +2189,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub clearClientSessionLocked(client); clearClientSessionForAccessibilityLocked(client); if (mCurClient == client) { - hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); if (mBoundToMethod) { mBoundToMethod = false; @@ -2232,9 +2203,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } mBoundToAccessibility = false; mCurClient = null; - if (mCurFocusedWindowClient == client) { - mCurFocusedWindowClient = null; - mCurFocusedWindowEditorInfo = null; + if (mImeBindingState.mFocusedWindowClient == client) { + mImeBindingState = ImeBindingState.newEmptyState(); } } } @@ -2283,7 +2253,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void onUnbindCurrentMethodByReset() { final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( - mCurFocusedWindow); + mImeBindingState.mFocusedWindow); if (winState != null && !winState.isRequestedImeVisible() && !mVisibilityStateComputer.isInputShown()) { // Normally, the focus window will apply the IME visibility state to @@ -2295,7 +2265,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // binding states in the first place. final var statsToken = createStatsTokenForFocusedClient(false /* show */, SoftInputShowHideReason.UNBIND_CURRENT_METHOD); - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken, STATE_HIDE_IME); + mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken, + STATE_HIDE_IME); } } @@ -2328,7 +2299,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean isShowRequestedForCurrentWindow() { final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull( - mCurFocusedWindow); + mImeBindingState.mFocusedWindow); return state != null && state.isRequestedImeVisible(); } @@ -2346,10 +2317,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub getCurTokenLocked(), mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.mUid), - mCurClient.mSelfReportedDisplayId, - mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode, - getSequenceNumberLocked()); - mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow); + mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, + mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked()); + mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow); mStartInputHistory.addEntry(info); // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user @@ -2376,7 +2346,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub : createStatsTokenForFocusedClient(true /* show */, SoftInputShowHideReason.ATTACH_NEW_INPUT); mCurStatsToken = null; - showCurrentInputLocked(mCurFocusedWindow, statsToken, + showCurrentInputLocked(mImeBindingState.mFocusedWindow, statsToken, mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } @@ -2450,7 +2420,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( - mCurFocusedWindow); + mImeBindingState.mFocusedWindow); if (winState == null) { return InputBindResult.NOT_IME_TARGET_WINDOW; } @@ -2469,7 +2439,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) { - hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE); return InputBindResult.NO_IME; } @@ -3638,7 +3608,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Binder.withCleanCallingIdentity(() -> { Objects.requireNonNull(windowToken, "windowToken must not be null"); synchronized (ImfLock.class) { - if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) { + if (mImeBindingState.mFocusedWindow != windowToken + || mCurPerceptible == perceptible) { return; } mCurPerceptible = perceptible; @@ -3732,7 +3703,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub super.hideSoftInputFromServerForTest_enforcePermission(); synchronized (ImfLock.class) { - hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_SOFT_INPUT); } } @@ -3907,7 +3878,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid); final boolean showForced = mVisibilityStateComputer.mShowForced; - if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) { + if (mImeBindingState.mFocusedWindow != windowToken + && showForced && shouldClearFlag) { mVisibilityStateComputer.mShowForced = false; } @@ -3925,7 +3897,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Slog.w(TAG, "If you need to impersonate a foreground user/profile from" + " a background user, use EditorInfo.targetInputMethodUser with" + " INTERACT_ACROSS_USERS_FULL permission."); - hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER); return InputBindResult.INVALID_USER; } @@ -3986,7 +3958,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + " cs=" + cs); } - final boolean sameWindowFocused = mCurFocusedWindow == windowToken; + final boolean sameWindowFocused = mImeBindingState.mFocusedWindow == windowToken; final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; final boolean startInputByWinGainedFocus = (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0; @@ -4017,10 +3989,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub null, null, null, null, -1, false); } - mCurFocusedWindow = windowToken; - mCurFocusedWindowSoftInputMode = softInputMode; - mCurFocusedWindowClient = cs; - mCurFocusedWindowEditorInfo = editorInfo; + mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo); mCurPerceptible = true; // We want to start input before showing the IME, but after closing @@ -4051,9 +4020,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub break; } final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason()); - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken, + mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken, imeVisRes.getState(), imeVisRes.getReason()); - if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) { // If focused display changed, we should unbind current method // to make app window in previous display relayout after Ime @@ -4092,7 +4060,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub throw new IllegalArgumentException("unknown client " + client.asBinder()); } ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN); - if (!isImeClientFocused(mCurFocusedWindow, cs)) { + if (!isImeClientFocused(mImeBindingState.mFocusedWindow, cs)) { Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client)); return false; } @@ -4104,8 +4072,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { final int uid = Binder.getCallingUid(); - if (mCurFocusedWindowClient != null && client != null - && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) { + if (mImeBindingState.mFocusedWindowClient != null && client != null + && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) { return true; } if (mSettings.getUserId() != UserHandle.getUserId(uid)) { @@ -4728,12 +4696,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); proto.write(CUR_SEQ, getSequenceNumberLocked()); proto.write(CUR_CLIENT, Objects.toString(mCurClient)); - proto.write(CUR_FOCUSED_WINDOW_NAME, - mWindowManagerInternal.getWindowName(mCurFocusedWindow)); + mImeBindingState.dumpDebug(proto, mWindowManagerInternal); proto.write(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName(mLastImeTargetWindow)); - proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, - InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)); + proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString( + mImeBindingState.mFocusedWindowSoftInputMode)); if (mCurEditorInfo != null) { mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } @@ -4855,12 +4822,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken); final WindowManagerInternal.ImeTargetInfo info = mWindowManagerInternal.onToggleImeRequested( - show, mCurFocusedWindow, requestToken, mCurTokenDisplayId); + show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId); mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( - mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName, - mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, - info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName, - info.imeSurfaceParentName)); + mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo, + info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason, + mInFullscreenMode, info.requestWindowName, info.imeControlTargetName, + info.imeLayerTargetName, info.imeSurfaceParentName)); if (statsToken != null) { mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName); @@ -5031,8 +4998,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_HIDE_ALL_INPUT_METHODS: synchronized (ImfLock.class) { @SoftInputShowHideReason final int reason = (int) msg.obj; - hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, reason); - + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, reason); } return true; case MSG_REMOVE_IME_SURFACE: { @@ -5051,7 +5017,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub IBinder windowToken = (IBinder) msg.obj; synchronized (ImfLock.class) { try { - if (windowToken == mCurFocusedWindow + if (windowToken == mImeBindingState.mFocusedWindow && mEnabledSession != null && mEnabledSession.mSession != null) { mEnabledSession.mSession.removeImeSurface(); } @@ -5126,7 +5092,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_START_HANDWRITING: synchronized (ImfLock.class) { IInputMethodInvoker curMethod = getCurMethodLocked(); - if (curMethod == null || mCurFocusedWindow == null) { + if (curMethod == null || mImeBindingState.mFocusedWindow == null) { return true; } final HandwritingModeController.HandwritingSession session = @@ -5134,7 +5100,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub msg.arg1 /*requestId*/, msg.arg2 /*pid*/, mBindingController.getCurMethodUid(), - mCurFocusedWindow); + mImeBindingState.mFocusedWindow); if (session == null) { Slog.e(TAG, "Failed to start handwriting session for requestId: " + msg.arg1); @@ -5187,11 +5153,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Handle IME visibility when interactive changed before finishing the input to // ensure we preserve the last state as possible. final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged( - mCurFocusedWindow, interactive); + mImeBindingState.mFocusedWindow, interactive); if (imeVisRes != null) { // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker. - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */, - imeVisRes.getState(), imeVisRes.getReason()); + mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, + null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason()); } // Eligible IME processes use new "setInteractive" protocol. mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode); @@ -5881,7 +5847,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void reportImeControl(@Nullable IBinder windowToken) { synchronized (ImfLock.class) { - if (mCurFocusedWindow != windowToken) { + if (mImeBindingState.mFocusedWindow != windowToken) { // mCurPerceptible was set by the focused window, but it is no longer in // control, so we reset mCurPerceptible. mCurPerceptible = true; @@ -5895,7 +5861,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Hide the IME method menu only when the IME surface parent is changed by the // input target changed, in case seeing the dialog dismiss flickering during // the next focused window starting the input connection. - if (mLastImeTargetWindow != mCurFocusedWindow) { + if (mLastImeTargetWindow != mImeBindingState.mFocusedWindow) { mMenuController.hideInputMethodMenuLocked(); } } @@ -6189,11 +6155,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); p.println(" mCurPerceptible=" + mCurPerceptible); - p.println(" mCurFocusedWindow=" + mCurFocusedWindow - + " softInputMode=" - + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode) - + " client=" + mCurFocusedWindowClient); - focusedWindowClient = mCurFocusedWindowClient; + mImeBindingState.dump(" ", p); p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + mBindingController.isVisibleBound()); @@ -6244,7 +6206,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub p.println("No input method client."); } - if (focusedWindowClient != null && client != focusedWindowClient) { + if (mImeBindingState.mFocusedWindowClient != null + && client != mImeBindingState.mFocusedWindowClient) { p.println(" "); p.println("Warning: Current input method client doesn't match the last focused. " + "window."); @@ -6252,7 +6215,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub p.println(" "); pw.flush(); try { - TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args); + TransferPipe.dumpAsync( + mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, args); } catch (IOException | RemoteException e) { p.println("Failed to dump input method client in focused window: " + e); } @@ -6698,7 +6662,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final String nextIme; final List<InputMethodInfo> nextEnabledImes; if (userId == mSettings.getUserId()) { - hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); mBindingController.unbindCurrentMethod(); @@ -6832,11 +6796,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @NonNull private ImeTracker.Token createStatsTokenForFocusedClient(boolean show, @SoftInputShowHideReason int reason) { - final int uid = mCurFocusedWindowClient != null - ? mCurFocusedWindowClient.mUid + final int uid = mImeBindingState.mFocusedWindowClient != null + ? mImeBindingState.mFocusedWindowClient.mUid : -1; - final var packageName = mCurFocusedWindowEditorInfo != null - ? mCurFocusedWindowEditorInfo.packageName + final var packageName = mImeBindingState.mFocusedWindowEditorInfo != null + ? mImeBindingState.mFocusedWindowEditorInfo.packageName : "uid(" + uid + ")"; return ImeTracker.forLogging().onStart(packageName, uid, diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 186cf5e37003..4bfd077760e4 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1080,7 +1080,7 @@ final class InstallPackageHelper { reconciledPackages = ReconcilePackageUtils.reconcilePackages( requests, Collections.unmodifiableMap(mPm.mPackages), versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings); + mPm.mSettings, mPm.mInjector.getSystemConfig()); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.setError("Reconciliation failed...", e); @@ -3810,7 +3810,7 @@ final class InstallPackageHelper { mPm.mPackages, Collections.singletonMap(pkgName, mPm.getSettingsVersionForPackage(parsedPackage)), mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings); + mPm.mSettings, mPm.mInjector.getSystemConfig()); if ((scanFlags & SCAN_AS_APEX) == 0) { appIdCreated = optimisticallyRegisterAppId(installRequest); } else { diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 9a7916a7b215..90d6adc4fa52 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -17,6 +17,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY; @@ -25,6 +26,7 @@ import static com.android.server.pm.PackageManagerService.SCAN_BOOTING; import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP; import static com.android.server.pm.PackageManagerService.TAG; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; @@ -36,6 +38,7 @@ import android.util.Slog; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.SystemConfig; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.utils.WatchedLongSparseArray; @@ -53,14 +56,17 @@ import java.util.Map; * as install) led to the request. */ final class ReconcilePackageUtils { - private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true; + // TODO(b/308573259): with allow-list, we should be able to disallow such installs even in + // debuggable builds. + private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS = Build.IS_DEBUGGABLE + || !Flags.restrictNonpreloadsSystemShareduids(); public static List<ReconciledPackage> reconcilePackages( List<InstallRequest> installRequests, Map<String, AndroidPackage> allPackages, Map<String, Settings.VersionInfo> versionInfos, SharedLibrariesImpl sharedLibraries, - KeySetManagerService ksms, Settings settings) + KeySetManagerService ksms, Settings settings, SystemConfig systemConfig) throws ReconcileFailure { final List<ReconciledPackage> result = new ArrayList<>(installRequests.size()); @@ -187,11 +193,19 @@ final class ReconcilePackageUtils { SigningDetails.CertCapabilities.PERMISSION)) { Slog.d(TAG, "Non-preload app associated with system signature: " + signatureCheckPs.getPackageName()); - if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) { - throw new ReconcileFailure( - INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, - "Non-preload app associated with system signature: " - + signatureCheckPs.getPackageName()); + if (sharedUserSetting != null && !ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS) { + // Check the allow-list. + var allowList = systemConfig.getPackageToSharedUidAllowList(); + var sharedUidName = allowList.get(signatureCheckPs.getPackageName()); + if (sharedUidName == null + || !sharedUserSetting.name.equals(sharedUidName)) { + var msg = "Non-preload app " + signatureCheckPs.getPackageName() + + " signed with platform signature and joining shared uid: " + + sharedUserSetting.name; + Slog.e(TAG, msg + ", allowList: " + allowList); + throw new ReconcileFailure( + INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, msg); + } } } diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java index 32a21c587f08..cebf7fb9a627 100644 --- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java +++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java @@ -21,7 +21,6 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_MEDIA_STORAGE; import static android.app.AppOpsManager.OP_LEGACY_STORAGE; import static android.app.AppOpsManager.OP_NONE; -import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; @@ -148,7 +147,7 @@ public abstract class SoftRestrictedPermissionPolicy { pkg.hasPreserveLegacyExternalStorage(); targetSDK = getMinimumTargetSDK(context, appInfo, user); - shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; + shouldApplyRestriction = !isWhiteListed; isForcedScopedStorage = sForcedScopedStorageAppWhitelist .contains(appInfo.packageName); } else { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 7d5aa96db0b8..d30a2167a183 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -553,7 +553,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean launchFailed; // set if a launched failed, to abort on 2nd try boolean delayedResume; // not yet resumed because of stopped app switches? boolean finishing; // activity in pending finish list? - int configChangeFlags; // which config values have changed private boolean keysPaused; // has key dispatching been paused for it? int launchMode; // the launch mode activity attribute. int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override @@ -1295,10 +1294,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mDeferHidingClient) { pw.println(prefix + "mDeferHidingClient=" + mDeferHidingClient); } - if (configChangeFlags != 0) { - pw.print(prefix); pw.print(" configChangeFlags="); - pw.println(Integer.toHexString(configChangeFlags)); - } if (mServiceConnectionsHolder != null) { pw.print(prefix); pw.print("connections="); pw.println(mServiceConnectionsHolder); } @@ -4064,7 +4059,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this); mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), - DestroyActivityItem.obtain(token, finishing, configChangeFlags)); + DestroyActivityItem.obtain(token, finishing)); } catch (Exception e) { // We can just ignore exceptions here... if the process has crashed, our death // notification will clean things up. @@ -4106,8 +4101,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - configChangeFlags = 0; - return removedFromHistory; } @@ -5026,7 +5019,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return PauseActivityItem.obtain(token); case STOPPING: case STOPPED: - return StopActivityItem.obtain(token, configChangeFlags); + return StopActivityItem.obtain(token); default: // Do not send a result immediately if the activity is in state INITIALIZING, // RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED. @@ -6300,7 +6293,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), PauseActivityItem.obtain(token, finishing, false /* userLeaving */, - configChangeFlags, false /* dontReport */, mAutoEnteringPip)); + false /* dontReport */, mAutoEnteringPip)); } catch (Exception e) { Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } @@ -6614,7 +6607,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A EventLogTags.writeWmStopActivity( mUserId, System.identityHashCode(this), shortComponentName); mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), - StopActivityItem.obtain(token, configChangeFlags)); + StopActivityItem.obtain(token)); mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT); } catch (Exception e) { @@ -9858,7 +9851,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (shouldRelaunchLocked(changes, mTmpConfig)) { // Aha, the activity isn't handling the change, so DIE DIE DIE. - configChangeFlags |= changes; if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating && !mTransitionController.isShellTransitionsEnabled()) { startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes); @@ -9887,7 +9879,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible " + "activity %s called by %s", this, Debug.getCallers(4)); } - relaunchActivityLocked(preserveWindow); + relaunchActivityLocked(preserveWindow, changes); // All done... tell the caller we weren't able to keep this activity around. return false; @@ -10029,9 +10021,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A | CONFIG_SCREEN_LAYOUT)) != 0; } - void relaunchActivityLocked(boolean preserveWindow) { + void relaunchActivityLocked(boolean preserveWindow, int configChangeFlags) { if (mAtmService.mSuppressResizeConfigChanges && preserveWindow) { - configChangeFlags = 0; return; } if (!preserveWindow) { @@ -10104,8 +10095,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The activity may be waiting for stop, but that is no longer appropriate for it. mTaskSupervisor.mStoppingActivities.remove(this); - - configChangeFlags = 0; } /** @@ -10174,7 +10163,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // {@link ActivityTaskManagerService.activityStopped}). try { mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), - StopActivityItem.obtain(token, 0 /* configChanges */)); + StopActivityItem.obtain(token)); } catch (RemoteException e) { Slog.w(TAG, "Exception thrown during restart " + this, e); } diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java index 64d8c7555fa6..86804360f6f4 100644 --- a/services/core/java/com/android/server/wm/SnapshotCache.java +++ b/services/core/java/com/android/server/wm/SnapshotCache.java @@ -16,7 +16,6 @@ package com.android.server.wm; import android.annotation.Nullable; -import android.hardware.HardwareBuffer; import android.util.ArrayMap; import android.window.TaskSnapshot; @@ -93,10 +92,6 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { if (entry != null) { mAppIdMap.remove(entry.topApp); mRunningCache.remove(id); - final HardwareBuffer buffer = entry.snapshot.getHardwareBuffer(); - if (buffer != null) { - buffer.close(); - } } } } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 85d81c4db2ca..78ababc6473f 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1881,7 +1881,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(), PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving, - prev.configChangeFlags, pauseImmediately, autoEnteringPip)); + pauseImmediately, autoEnteringPip)); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e19f08cb04a1..9d95c5b4f649 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2774,9 +2774,12 @@ public final class SystemServer implements Dumpable { t.traceEnd(); // OnDevicePersonalizationSystemService - t.traceBegin("StartOnDevicePersonalizationSystemService"); - mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS); - t.traceEnd(); + if (!com.android.server.flags.Flags.enableOdpFeatureGuard() + || SystemProperties.getBoolean("ro.system_settings.service.odp_enabled", true)) { + t.traceBegin("StartOnDevicePersonalizationSystemService"); + mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS); + t.traceEnd(); + } // Profiling if (android.server.Flags.telemetryApisService()) { diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 4b086b3aca17..67df67fdf6c1 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -227,25 +227,59 @@ class AppIdPermissionPolicy : SchemePolicy() { if (isRequestedBySystemPackage) { return@forEach } - val oldFlags = getPermissionFlags(appId, userId, permissionName) - var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT - val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) - newFlags = - if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = - if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } - setPermissionFlags(appId, userId, permissionName, newFlags) + updatePermissionExemptFlags( + appId, + userId, + permission, + PermissionFlags.UPGRADE_EXEMPT, + 0 + ) } } + fun MutateStateScope.updatePermissionExemptFlags( + appId: Int, + userId: Int, + permission: Permission, + exemptFlagMask: Int, + exemptFlagValues: Int + ) { + val permissionName = permission.name + val oldFlags = getPermissionFlags(appId, userId, permissionName) + var newFlags = (oldFlags andInv exemptFlagMask) or (exemptFlagValues and exemptFlagMask) + if (oldFlags == newFlags) { + return + } + val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) + if (permission.isHardRestricted && !isExempt) { + newFlags = newFlags or PermissionFlags.RESTRICTION_REVOKED + // If the permission was policy fixed as granted but it is no longer on any of the + // allowlists we need to clear the policy fixed flag as allowlisting trumps policy i.e. + // policy cannot grant a non grantable permission. + if (PermissionFlags.isPermissionGranted(oldFlags)) { + newFlags = newFlags andInv PermissionFlags.POLICY_FIXED + } + } else { + newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if ( + permission.isSoftRestricted && !isExempt && + !anyPackageInAppId(appId) { + permissionName in it.androidPackage!!.requestedPermissions && + isSoftRestrictedPermissionExemptForPackage(it, permissionName) + } + ) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } + if (oldFlags == newFlags) { + return + } + setPermissionFlags(appId, userId, permissionName, newFlags) + } + override fun MutateStateScope.onPackageUninstalled( packageName: String, appId: Int, @@ -1118,7 +1152,12 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags andInv PermissionFlags.RESTRICTION_REVOKED } newFlags = - if (permission.isSoftRestricted && !isExempt) { + if ( + permission.isSoftRestricted && !isExempt && + !requestingPackageStates.anyIndexed { _, it -> + isSoftRestrictedPermissionExemptForPackage(it, permissionName) + } + ) { newFlags or PermissionFlags.SOFT_RESTRICTED } else { newFlags andInv PermissionFlags.SOFT_RESTRICTED @@ -1398,6 +1437,17 @@ class AppIdPermissionPolicy : SchemePolicy() { } } + // See also SoftRestrictedPermissionPolicy.mayGrantPermission() + private fun isSoftRestrictedPermissionExemptForPackage( + packageState: PackageState, + permissionName: String + ): Boolean = + when (permissionName) { + Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE -> + packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q + else -> false + } + private inline fun MutateStateScope.anyPackageInAppId( appId: Int, state: AccessState = newState, diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt index 28889de4bbb1..c5c921dc3515 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -346,9 +346,18 @@ object PermissionFlags { return flags.hasBits(RUNTIME_GRANTED) } - fun isAppOpGranted(flags: Int): Boolean = - isPermissionGranted(flags) && !flags.hasBits(RESTRICTION_REVOKED) && - !flags.hasBits(APP_OP_REVOKED) + fun isAppOpGranted(flags: Int): Boolean { + if (!isPermissionGranted(flags)) { + return false + } + if (flags.hasAnyBit(MASK_RESTRICTED)) { + return false + } + if (flags.hasBits(APP_OP_REVOKED)) { + return false + } + return true + } fun toApiFlags(flags: Int): Int { var apiFlags = 0 diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 0c6c4da712bb..1f654631f902 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -88,7 +88,6 @@ import com.android.server.pm.PackageInstallerService import com.android.server.pm.PackageManagerLocal import com.android.server.pm.UserManagerInternal import com.android.server.pm.UserManagerService -import com.android.server.pm.parsing.pkg.AndroidPackageUtils import com.android.server.pm.permission.LegacyPermission import com.android.server.pm.permission.LegacyPermissionSettings import com.android.server.pm.permission.LegacyPermissionState @@ -97,7 +96,6 @@ import com.android.server.pm.permission.PermissionManagerServiceInterface import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState -import com.android.server.policy.SoftRestrictedPermissionPolicy import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.CompletableFuture @@ -1006,25 +1004,14 @@ class PermissionService(private val service: AccessCheckingService) : } if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) { - // TODO: Refactor SoftRestrictedPermissionPolicy. - val softRestrictedPermissionPolicy = - SoftRestrictedPermissionPolicy.forPermission( - context, - AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), - androidPackage, - UserHandle.of(userId), - permissionName + if (reportError) { + Slog.e( + LOG_TAG, + "$methodName: Cannot grant soft-restricted non-exempt permission" + + " $permissionName to package $packageName" ) - if (!softRestrictedPermissionPolicy.mayGrantPermission()) { - if (reportError) { - Slog.e( - LOG_TAG, - "$methodName: Cannot grant soft-restricted non-exempt permission" + - " $permissionName to package $packageName" - ) - } - return } + return } val newFlags = PermissionFlags.updateRuntimePermissionGranted(oldFlags, isGranted) @@ -1850,10 +1837,19 @@ class PermissionService(private val service: AccessCheckingService) : allowlistedFlags: Int, userId: Int ) { + var exemptMask = 0 + if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) { + exemptMask = exemptMask or PermissionFlags.SYSTEM_EXEMPT + } + if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) { + exemptMask = exemptMask or PermissionFlags.UPGRADE_EXEMPT + } + if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) { + exemptMask = exemptMask or PermissionFlags.INSTALLER_EXEMPT + } + service.mutateState { with(policy) { - val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState - val permissions = getPermissions() androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission -> val permission = permissions[requestedPermission] @@ -1861,81 +1857,8 @@ class PermissionService(private val service: AccessCheckingService) : return@forEachIndexed } - val oldFlags = permissionsFlags[requestedPermission] ?: 0 - val wasGranted = PermissionFlags.isPermissionGranted(oldFlags) - - var newFlags = oldFlags - var mask = 0 - var allowlistFlagsCopy = allowlistedFlags - while (allowlistFlagsCopy != 0) { - val flag = 1 shl allowlistFlagsCopy.countTrailingZeroBits() - allowlistFlagsCopy = allowlistFlagsCopy and flag.inv() - when (flag) { - PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM -> { - mask = mask or PermissionFlags.SYSTEM_EXEMPT - newFlags = - if (permissionNames.contains(requestedPermission)) { - newFlags or PermissionFlags.SYSTEM_EXEMPT - } else { - newFlags andInv PermissionFlags.SYSTEM_EXEMPT - } - } - PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE -> { - mask = mask or PermissionFlags.UPGRADE_EXEMPT - newFlags = - if (permissionNames.contains(requestedPermission)) { - newFlags or PermissionFlags.UPGRADE_EXEMPT - } else { - newFlags andInv PermissionFlags.UPGRADE_EXEMPT - } - } - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER -> { - mask = mask or PermissionFlags.INSTALLER_EXEMPT - newFlags = - if (permissionNames.contains(requestedPermission)) { - newFlags or PermissionFlags.INSTALLER_EXEMPT - } else { - newFlags andInv PermissionFlags.INSTALLER_EXEMPT - } - } - } - } - - if (oldFlags == newFlags) { - return@forEachIndexed - } - - val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) - - // If the permission is policy fixed as granted but it is no longer - // on any of the allowlists we need to clear the policy fixed flag - // as allowlisting trumps policy i.e. policy cannot grant a non - // grantable permission. - if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) { - if (!isExempt && wasGranted) { - mask = mask or PermissionFlags.POLICY_FIXED - newFlags = newFlags andInv PermissionFlags.POLICY_FIXED - } - } - - newFlags = - if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = - if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } - mask = - mask or - PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SOFT_RESTRICTED - - updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags) + var exemptFlags = if (requestedPermission in permissionNames) exemptMask else 0 + updatePermissionExemptFlags(appId, userId, permission, exemptMask, exemptFlags) } } } diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt index cde46abafe95..96753b6d2bcc 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt @@ -233,24 +233,6 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() { .isEqualTo(expectedNewFlags) } - @Test - fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() { - val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT - testOnPackageInstalled( - oldFlags, - permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED - ) {} - val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) - val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT - assertWithMessage( - "After onPackageInstalled() is called for a non-system app that requests a runtime" + - " soft restricted permission that is exempted. The actual permission flags" + - " $actualFlags should match the expected flags $expectedNewFlags" - ) - .that(actualFlags) - .isEqualTo(expectedNewFlags) - } - private fun testOnPackageInstalled( oldFlags: Int, permissionInfoFlags: Int = 0, diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 60130635108c..da11e6ad613a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -554,7 +554,7 @@ public class RootWindowContainerTests extends WindowTestsBase { mRootWindowContainer.applySleepTokens(true); // The display orientation should be changed by the activity so there is no relaunch. - verify(activity, never()).relaunchActivityLocked(anyBoolean()); + verify(activity, never()).relaunchActivityLocked(anyBoolean(), anyInt()); assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation); } diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index 7343ba1c1ce7..e60764f137af 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.content.pm.ServiceInfo +import android.hardware.input.KeyboardLayoutSelectionResult import android.hardware.input.IInputManager import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal @@ -525,13 +526,13 @@ class KeyboardLayoutManagerTests { keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, ENGLISH_UK_LAYOUT_DESCRIPTOR ) - val keyboardLayout = + assertEquals( + "Default UI: getKeyboardLayoutForInputDevice API should always return " + + "KeyboardLayoutSelectionResult.FAILED", + KeyboardLayoutSelectionResult.FAILED, keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype ) - assertNull( - "Default UI: getKeyboardLayoutForInputDevice API should always return null", - keyboardLayout ) } } @@ -545,12 +546,14 @@ class KeyboardLayoutManagerTests { keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, ENGLISH_UK_LAYOUT_DESCRIPTOR ) - assertEquals( - "New UI: getKeyboardLayoutForInputDevice API should return the set layout", - ENGLISH_UK_LAYOUT_DESCRIPTOR, + var result = keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype ) + assertEquals( + "New UI: getKeyboardLayoutForInputDevice API should return the set layout", + ENGLISH_UK_LAYOUT_DESCRIPTOR, + result.layoutDescriptor ) // This should replace previously set layout @@ -558,12 +561,14 @@ class KeyboardLayoutManagerTests { keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, ENGLISH_US_LAYOUT_DESCRIPTOR ) - assertEquals( - "New UI: getKeyboardLayoutForInputDevice API should return the last set layout", - ENGLISH_US_LAYOUT_DESCRIPTOR, + result = keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype ) + assertEquals( + "New UI: getKeyboardLayoutForInputDevice API should return the last set layout", + ENGLISH_US_LAYOUT_DESCRIPTOR, + result.layoutDescriptor ) } } @@ -734,17 +739,20 @@ class KeyboardLayoutManagerTests { createImeSubtypeForLanguageTag("ru"), createLayoutDescriptor("keyboard_layout_russian") ) - assertNull( - "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " + - "layout available", + assertEquals( + "New UI: getDefaultKeyboardLayoutForInputDevice should return " + + "KeyboardLayoutSelectionResult.FAILED when no layout available", + KeyboardLayoutSelectionResult.FAILED, keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, createImeSubtypeForLanguageTag("it") ) ) - assertNull( - "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " + - "layout for script code is available", + assertEquals( + "New UI: getDefaultKeyboardLayoutForInputDevice should return " + + "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" + + "available", + KeyboardLayoutSelectionResult.FAILED, keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, createImeSubtypeForLanguageTag("en-Deva") @@ -811,8 +819,10 @@ class KeyboardLayoutManagerTests { createImeSubtypeForLanguageTagAndLayoutType("ru", ""), createLayoutDescriptor("keyboard_layout_russian") ) - assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " + - "no layout for script code is available", + assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " + + "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" + + "available", + KeyboardLayoutSelectionResult.FAILED, keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "") @@ -865,14 +875,16 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.anyBoolean(), ArgumentMatchers.eq(keyboardDevice.vendorId), ArgumentMatchers.eq(keyboardDevice.productId), - ArgumentMatchers.eq(createByteArray( + ArgumentMatchers.eq( + createByteArray( KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, LAYOUT_TYPE_DEFAULT, GERMAN_LAYOUT_NAME, - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, "de-Latn", - LAYOUT_TYPE_QWERTZ), + LAYOUT_TYPE_QWERTZ ), + ), ArgumentMatchers.eq(keyboardDevice.deviceBus), ) } @@ -893,13 +905,16 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.anyBoolean(), ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId), ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId), - ArgumentMatchers.eq(createByteArray( + ArgumentMatchers.eq( + createByteArray( "en", LAYOUT_TYPE_QWERTY, ENGLISH_US_LAYOUT_NAME, - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE, "de-Latn", - LAYOUT_TYPE_QWERTZ)), + LAYOUT_TYPE_QWERTZ + ) + ), ArgumentMatchers.eq(keyboardDevice.deviceBus), ) } @@ -918,14 +933,16 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.anyBoolean(), ArgumentMatchers.eq(keyboardDevice.vendorId), ArgumentMatchers.eq(keyboardDevice.productId), - ArgumentMatchers.eq(createByteArray( + ArgumentMatchers.eq( + createByteArray( KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, LAYOUT_TYPE_DEFAULT, "Default", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT, KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, - LAYOUT_TYPE_DEFAULT), + LAYOUT_TYPE_DEFAULT ), + ), ArgumentMatchers.eq(keyboardDevice.deviceBus), ) } @@ -998,12 +1015,13 @@ class KeyboardLayoutManagerTests { imeSubtype: InputMethodSubtype, expectedLayout: String ) { + val result = keyboardLayoutManager.getKeyboardLayoutForInputDevice( + device.identifier, USER_ID, imeInfo, imeSubtype + ) assertEquals( "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout", expectedLayout, - keyboardLayoutManager.getKeyboardLayoutForInputDevice( - device.identifier, USER_ID, imeInfo, imeSubtype - ) + result.layoutDescriptor ) } diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt index 89a47b9b736a..0615941eda09 100644 --- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt @@ -17,6 +17,7 @@ package com.android.server.input import android.hardware.input.KeyboardLayout +import android.hardware.input.KeyboardLayoutSelectionResult import android.icu.util.ULocale import android.platform.test.annotations.Presubmit import android.view.InputDevice @@ -120,15 +121,15 @@ class KeyboardMetricsCollectorTests { val event = builder.addLayoutSelection( createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"), "English(US)(Qwerty)", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD ).addLayoutSelection( createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"), null, // Default layout type - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER ).addLayoutSelection( createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"), "German", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE ).setIsFirstTimeConfiguration(true).build() assertEquals( @@ -158,7 +159,7 @@ class KeyboardMetricsCollectorTests { "de-CH", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"), "English(US)(Qwerty)", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, "en-US", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"), ) @@ -167,7 +168,7 @@ class KeyboardMetricsCollectorTests { "de-CH", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"), KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME, - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER, "en-US", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"), ) @@ -176,7 +177,7 @@ class KeyboardMetricsCollectorTests { "de-CH", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"), "German", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE, "en-US", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"), ) @@ -197,7 +198,7 @@ class KeyboardMetricsCollectorTests { val event = builder.addLayoutSelection( createImeSubtype(4, null, "qwerty"), // Default language tag "German", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE ).build() assertExpectedLayoutConfiguration( @@ -205,7 +206,7 @@ class KeyboardMetricsCollectorTests { KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"), "German", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE, KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"), ) |