Merge "[SB][Screen Chips] Add CUJ java mappings for launching chip dialog." into main
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 762e2af..0750c8d 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -145,7 +145,9 @@
Intent.ACTION_USER_STARTED,
Intent.ACTION_MEDIA_MOUNTED,
Intent.ACTION_USER_UNLOCKED,
- Intent.ACTION_USER_STOPPED);
+ Intent.ACTION_USER_STOPPED,
+ Intent.ACTION_PROFILE_AVAILABLE,
+ Intent.ACTION_PROFILE_UNAVAILABLE);
mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND);
removeAnyPreviousTestUsers();
if (mAm.getCurrentUser() != UserHandle.USER_SYSTEM) {
@@ -1230,6 +1232,255 @@
}
}
+ /** Tests creating a private profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void createPrivateProfile() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ Log.i(TAG, "Starting timer");
+
+ final int userId = createPrivateProfileUser();
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests creating and starting a newly created private profile. This test waits for the
+ * {@link Intent.ACTION_USER_STARTED} to be received.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void createAndStartPrivateProfile_realistic() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ Log.i(TAG, "Starting timer");
+ final int userId = createPrivateProfileUser();
+
+ // Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
+ // ACTION_USER_STARTED.
+ runThenWaitForBroadcasts(userId, () -> {
+ mIam.startUserInBackground(userId);
+ }, Intent.ACTION_USER_STARTED);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /** Tests for stopping the private profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileStopped() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ runThenWaitForBroadcasts(userId, () -> {
+ startUserInBackgroundAndWaitForUnlock(userId);
+ }, Intent.ACTION_MEDIA_MOUNTED);
+
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ stopUser(userId);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests unlocking a newly-created private profile using the
+ * {@link UserManager#requestQuietModeEnabled} api.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileUnlock() throws RemoteException {
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+ waitCoolDownPeriod();
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ mUm.requestQuietModeEnabled(false, UserHandle.of(userId));
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests unlocking a newly-created private profile using the
+ * {@link UserManager#requestQuietModeEnabled} api and waiting for the
+ * {@link Intent.ACTION_PROFILE_AVAILABLE} to be received.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileUnlock_realistic() throws RemoteException {
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+ waitCoolDownPeriod();
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ runThenWaitForBroadcasts(userId, () -> {
+ mUm.requestQuietModeEnabled(false, UserHandle.of(userId));
+ }, Intent.ACTION_PROFILE_AVAILABLE);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests locking a newly-created private profile using the
+ * {@link UserManager#requestQuietModeEnabled} api.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileLock() throws RemoteException {
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests locking a newly-created private profile using the
+ * {@link UserManager#requestQuietModeEnabled} api and waiting for the
+ * {@link Intent.ACTION_PROFILE_UNAVAILABLE} to be received.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileLock_realistic() throws RemoteException {
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ runThenWaitForBroadcasts(userId, () -> {
+ mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+ }, Intent.ACTION_PROFILE_UNAVAILABLE);
+
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /** Tests removing a newly-created private profile */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileRemove() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+ removeUser(userId);
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /** Tests installing a pre-existing app in a private profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileInstall() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests creating a new private profile, starting it, installing an app, and launching that app
+ * in it.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileCreateStartInstallAndLaunchApp() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+ startApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests starting & launching an already-installed app in a private profile.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileStartAndLaunchApp() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ startUserInBackgroundAndWaitForUnlock(userId);
+ startApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Creates a new user, returning its userId. */
private int createUserNoFlags() {
return createUserWithFlags(/* flags= */ 0);
@@ -1252,6 +1503,18 @@
return userInfo.id;
}
+ /** Creates a private profile under the current user, returning its userId. */
+ private int createPrivateProfileUser() {
+ final UserInfo userInfo = mUm.createProfileForUser(TEST_USER_NAME,
+ UserManager.USER_TYPE_PROFILE_PRIVATE, /* flags */ 0, mAm.getCurrentUser());
+ attestFalse(
+ "Creating a private profile failed. Most likely there is already a pre-existing "
+ + "private profile on the device.",
+ userInfo == null);
+ mUsersToRemove.add(userInfo.id);
+ return userInfo.id;
+ }
+
/**
* Start user in background and wait for it to unlock by waiting for
* UserState.mUnlockProgress.finish().
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 5efda98..bf67187 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -75,10 +75,10 @@
fun getClassFlag(classItem: ClassItem): String? {
var classFlag = getFlagAnnotation(classItem)
var cur = classItem
- // If a class is not an inner class, use its @FlaggedApi annotation value.
+ // If a class is not a nested class, use its @FlaggedApi annotation value.
// Otherwise, use the flag value of the closest outer class that is annotated by @FlaggedApi.
- while (cur.isInnerClass() && classFlag == null) {
- cur = cur.parent() as ClassItem
+ while (classFlag == null) {
+ cur = cur.containingClass() ?: break
classFlag = getFlagAnnotation(cur)
}
return classFlag
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 40d4fb6..1767d64 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -32,6 +32,7 @@
import android.os.CombinedVibration;
import android.hardware.input.IInputSensorEventListener;
import android.hardware.input.InputSensorInfo;
+import android.hardware.input.KeyGlyphMap;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
import android.os.IBinder;
@@ -236,4 +237,6 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)")
void unregisterStickyModifierStateListener(IStickyModifierStateListener listener);
+
+ KeyGlyphMap getKeyGlyphMap(int deviceId);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 21c9002..23e262c 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,6 +19,7 @@
import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
+import static com.android.hardware.input.Flags.keyboardGlyphMap;
import android.Manifest;
import android.annotation.FlaggedApi;
@@ -158,6 +159,34 @@
"android.hardware.input.metadata.KEYBOARD_LAYOUTS";
/**
+ * Broadcast Action: Query available keyboard glyph maps.
+ * <p>
+ * The input manager service locates available keyboard glyph maps
+ * by querying broadcast receivers that are registered for this action.
+ * An application can offer additional keyboard glyph maps to the user
+ * by declaring a suitable broadcast receiver in its manifest.
+ * </p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_QUERY_KEYBOARD_GLYPH_MAPS =
+ "android.hardware.input.action.QUERY_KEYBOARD_GLYPH_MAPS";
+
+ /**
+ * Metadata Key: Keyboard glyph map metadata associated with
+ * {@link #ACTION_QUERY_KEYBOARD_GLYPH_MAPS}.
+ * <p>
+ * Specifies the resource id of a XML resource that describes the keyboard
+ * glyph maps that are provided by the application.
+ * </p>
+ *
+ * @hide
+ */
+ public static final String META_DATA_KEYBOARD_GLYPH_MAPS =
+ "android.hardware.input.metadata.KEYBOARD_GLYPH_MAPS";
+
+ /**
* Prevent touches from being consumed by apps if these touches passed through a non-trusted
* window from a different UID and are considered unsafe.
*
@@ -898,6 +927,23 @@
}
/**
+ * Provides associated glyph map for the keyboard device (if available)
+ *
+ * @hide
+ */
+ @Nullable
+ public KeyGlyphMap getKeyGlyphMap(int deviceId) {
+ if (!keyboardGlyphMap()) {
+ return null;
+ }
+ try {
+ return mIm.getKeyGlyphMap(deviceId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Injects an input event into the event system, targeting windows owned by the provided uid.
*
* If a valid targetUid is provided, the system will only consider injecting the input event
diff --git a/core/java/android/hardware/input/KeyGlyphMap.aidl b/core/java/android/hardware/input/KeyGlyphMap.aidl
new file mode 100644
index 0000000..104f1e4
--- /dev/null
+++ b/core/java/android/hardware/input/KeyGlyphMap.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 KeyGlyphMap;
\ No newline at end of file
diff --git a/core/java/android/hardware/input/KeyGlyphMap.java b/core/java/android/hardware/input/KeyGlyphMap.java
new file mode 100644
index 0000000..49c47a2
--- /dev/null
+++ b/core/java/android/hardware/input/KeyGlyphMap.java
@@ -0,0 +1,187 @@
+/*
+ * 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.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.KeyEvent;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class provides access to device specific key glyphs, modifier glyphs and device specific
+ * shortcuts and keys
+ *
+ * @hide
+ */
+public final class KeyGlyphMap implements Parcelable {
+ private static final String TAG = "KeyGlyphMap";
+
+ @NonNull
+ private final ComponentName mComponentName;
+ @NonNull
+ private final SparseIntArray mKeyGlyphs;
+ @NonNull
+ private final SparseIntArray mModifierGlyphs;
+ @NonNull
+ private final int[] mFunctionRowKeys;
+ @NonNull
+ private final Map<KeyCombination, Integer> mHardwareShortcuts;
+
+ public static final @NonNull Parcelable.Creator<KeyGlyphMap> CREATOR =
+ new Parcelable.Creator<>() {
+ public KeyGlyphMap createFromParcel(Parcel in) {
+ return new KeyGlyphMap(in);
+ }
+
+ public KeyGlyphMap[] newArray(int size) {
+ return new KeyGlyphMap[size];
+ }
+ };
+
+ public KeyGlyphMap(@NonNull ComponentName componentName,
+ @NonNull SparseIntArray keyGlyphs, @NonNull SparseIntArray modifierGlyphs,
+ @NonNull int[] functionRowKeys,
+ @NonNull Map<KeyCombination, Integer> hardwareShortcuts) {
+ mComponentName = componentName;
+ mKeyGlyphs = keyGlyphs;
+ mModifierGlyphs = modifierGlyphs;
+ mFunctionRowKeys = functionRowKeys;
+ mHardwareShortcuts = hardwareShortcuts;
+ }
+
+ public KeyGlyphMap(Parcel in) {
+ mComponentName = in.readParcelable(getClass().getClassLoader(), ComponentName.class);
+ mKeyGlyphs = in.readSparseIntArray();
+ mModifierGlyphs = in.readSparseIntArray();
+ mFunctionRowKeys = new int[in.readInt()];
+ in.readIntArray(mFunctionRowKeys);
+ mHardwareShortcuts = new HashMap<>(in.readInt());
+ in.readMap(mHardwareShortcuts, getClass().getClassLoader(), KeyCombination.class,
+ Integer.class);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mComponentName, 0);
+ dest.writeSparseIntArray(mKeyGlyphs);
+ dest.writeSparseIntArray(mModifierGlyphs);
+ dest.writeInt(mFunctionRowKeys.length);
+ dest.writeIntArray(mFunctionRowKeys);
+ dest.writeInt(mHardwareShortcuts.size());
+ dest.writeMap(mHardwareShortcuts);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Defines a key combination that includes a keycode and modifier state.
+ */
+ public record KeyCombination(int modifierState, int keycode) {}
+
+ /**
+ * Returns keycodes generated from the functional row defined for the keyboard.
+ */
+ public int[] getFunctionRowKeys() {
+ return mFunctionRowKeys;
+ }
+
+ /**
+ * Returns hardware defined shortcuts that are handled in the firmware of a particular
+ * keyboard (e.g. Fn+Backspace = Back, etc.)
+ *
+ * @return a map of (modifier + key) combinations to keycode mappings that are handled by the
+ * device hardware/firmware.
+ */
+ public Map<KeyCombination, Integer> getHardwareShortcuts() {
+ return mHardwareShortcuts;
+ }
+
+ /**
+ * Provides the drawable resource for the glyph for a keycode.
+ * Returns null if not available.
+ */
+ @Nullable
+ public Drawable getDrawableForKeycode(Context context, int keycode) {
+ return getDrawable(context, mKeyGlyphs.get(keycode, 0));
+ }
+
+ /**
+ * Provides the drawable resource for the glyph for a modifier key.
+ * Returns null if not available.
+ */
+ @Nullable
+ public Drawable getDrawableForModifier(Context context, int modifierKeycode) {
+ int modifier = switch (modifierKeycode) {
+ case KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT -> KeyEvent.META_META_ON;
+ case KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT -> KeyEvent.META_CTRL_ON;
+ case KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT -> KeyEvent.META_ALT_ON;
+ case KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT ->
+ KeyEvent.META_SHIFT_ON;
+ case KeyEvent.KEYCODE_FUNCTION -> KeyEvent.META_FUNCTION_ON;
+ case KeyEvent.KEYCODE_SYM -> KeyEvent.META_SYM_ON;
+ case KeyEvent.KEYCODE_CAPS_LOCK -> KeyEvent.META_CAPS_LOCK_ON;
+ case KeyEvent.KEYCODE_NUM_LOCK -> KeyEvent.META_NUM_LOCK_ON;
+ case KeyEvent.KEYCODE_SCROLL_LOCK -> KeyEvent.META_SCROLL_LOCK_ON;
+ default -> 0;
+ };
+ return getDrawable(context, mModifierGlyphs.get(modifier, 0));
+ }
+
+ @Nullable
+ private Drawable getDrawable(Context context, @DrawableRes int drawableRes) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ ActivityInfo receiver = pm.getReceiverInfo(mComponentName,
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+ return resources.getDrawable(drawableRes, null);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ Log.e(TAG, "Package name not found for " + mComponentName);
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "KeyGlyphMap{"
+ + "mComponentName=" + mComponentName
+ + ", mKeyGlyphs=" + mKeyGlyphs
+ + ", mModifierGlyphs=" + mModifierGlyphs
+ + ", mFunctionRowKeys=" + Arrays.toString(mFunctionRowKeys)
+ + ", mHardwareShortcuts=" + mHardwareShortcuts
+ + '}';
+ }
+}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index ed536ce..acd0d00f 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -53,4 +53,11 @@
name: "touchpad_tap_dragging"
description: "Offers a setting to enable touchpad tap dragging"
bug: "321978150"
-}
\ No newline at end of file
+}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_glyph_map"
+ description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
+ bug: "345440920"
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7926afe..f30a9f5 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -6367,6 +6367,33 @@
Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
}
+ private static final String CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY =
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "user_serial_number");
+
+ private final PropertyInvalidatedCache<Integer, Integer> mUserSerialNumberCache =
+ new PropertyInvalidatedCache<Integer, Integer>(
+ 32, CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY) {
+ @Override
+ public Integer recompute(Integer query) {
+ try {
+ return mService.getUserSerialNumber(query);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query <= 0;
+ }
+ };
+
+
+ /** @hide */
+ public static final void invalidateUserSerialNumberCache() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY);
+ }
+
/**
* Returns a serial number on this device for a given userId. User handles can be recycled
* when deleting and creating users, but serial numbers are not reused until the device is wiped.
@@ -6376,6 +6403,14 @@
*/
@UnsupportedAppUsage
public int getUserSerialNumber(@UserIdInt int userId) {
+ if (android.multiuser.Flags.cacheUserSerialNumber()) {
+ // System user serial number is always 0, and it always exists.
+ // There is no need to call binder for that.
+ if (userId == UserHandle.USER_SYSTEM) {
+ return UserHandle.USER_SERIAL_SYSTEM;
+ }
+ return mUserSerialNumberCache.query(userId);
+ }
try {
return mService.getUserSerialNumber(userId);
} catch (RemoteException re) {
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index e029e52..5ef597d 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -213,3 +213,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "location_bypass_privacy_dashboard_enabled"
+ is_exported: true
+ namespace: "permissions"
+ description: "Show access entry of location bypass permission in the Privacy Dashboard"
+ bug: "325536053"
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 2948129..6b0d301 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1405,7 +1405,8 @@
convertToComponentName(
rawMetadata.getString(
com.android.internal.R.styleable.Dream_settingsActivity),
- serviceInfo),
+ serviceInfo,
+ packageManager),
rawMetadata.getDrawable(
com.android.internal.R.styleable.Dream_previewImage),
rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
@@ -1420,26 +1421,38 @@
}
@Nullable
- private static ComponentName convertToComponentName(@Nullable String flattenedString,
- ServiceInfo serviceInfo) {
+ private static ComponentName convertToComponentName(
+ @Nullable String flattenedString,
+ ServiceInfo serviceInfo,
+ PackageManager packageManager) {
if (flattenedString == null) {
return null;
}
- if (!flattenedString.contains("/")) {
- return new ComponentName(serviceInfo.packageName, flattenedString);
+ final ComponentName cn =
+ flattenedString.contains("/")
+ ? ComponentName.unflattenFromString(flattenedString)
+ : new ComponentName(serviceInfo.packageName, flattenedString);
+
+ if (cn == null) {
+ return null;
}
// Ensure that the component is from the same package as the dream service. If not,
// treat the component as invalid and return null instead.
- final ComponentName cn = ComponentName.unflattenFromString(flattenedString);
- if (cn == null) return null;
if (!cn.getPackageName().equals(serviceInfo.packageName)) {
Log.w(TAG,
"Inconsistent package name in component: " + cn.getPackageName()
+ ", should be: " + serviceInfo.packageName);
return null;
}
+
+ // Ensure that the activity exists. If not, treat the component as invalid and return null.
+ if (new Intent().setComponent(cn).resolveActivityInfo(packageManager, 0) == null) {
+ Log.w(TAG, "Dream settings activity not found: " + cn);
+ return null;
+ }
+
return cn;
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 02ea6d4..df2af73 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -29,6 +29,7 @@
import static android.view.WindowInsets.Type.ime;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.window.flags.Flags.insetsControlSeq;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -890,7 +891,9 @@
@InsetsType int visibleTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
for (int i = 0, size = newState.sourceSize(); i < size; i++) {
- final InsetsSource source = newState.sourceAt(i);
+ final InsetsSource source = insetsControlSeq()
+ ? new InsetsSource(newState.sourceAt(i))
+ : newState.sourceAt(i);
@InsetsType int type = source.getType();
@AnimationType int animationType = getAnimationType(type);
final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId());
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 6a92fd9..c73cbc6 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -28,6 +28,7 @@
import static android.view.InsetsSourceConsumerProto.TYPE_NUMBER;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.window.flags.Flags.insetsControlSeq;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -410,7 +411,9 @@
// Frame is changing while animating. Keep note of the new frame but keep existing frame
// until animation is finished.
- newSource = new InsetsSource(newSource);
+ if (!insetsControlSeq()) {
+ newSource = new InsetsSource(newSource);
+ }
mPendingFrame = new Rect(newSource.getFrame());
mPendingVisibleFrame = newSource.getVisibleFrame() != null
? new Rect(newSource.getVisibleFrame())
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 6420620..a3fcfad 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -72,6 +72,7 @@
UserShortcutType.TRIPLETAP,
UserShortcutType.TWOFINGER_DOUBLETAP,
UserShortcutType.QUICK_SETTINGS,
+ UserShortcutType.GESTURE
})
public @interface UserShortcutType {
int DEFAULT = 0;
@@ -81,6 +82,7 @@
int TRIPLETAP = 1 << 2;
int TWOFINGER_DOUBLETAP = 1 << 3;
int QUICK_SETTINGS = 1 << 4;
+ int GESTURE = 1 << 5;
// LINT.ThenChange(:shortcut_type_array)
}
@@ -95,6 +97,7 @@
UserShortcutType.TRIPLETAP,
UserShortcutType.TWOFINGER_DOUBLETAP,
UserShortcutType.QUICK_SETTINGS,
+ UserShortcutType.GESTURE
// LINT.ThenChange(:shortcut_type_intdef)
};
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
index c8d6194..01cbb55 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
@@ -20,6 +20,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityButtonLongPressStatus;
@@ -66,6 +67,9 @@
NAV_BAR_MODE_GESTURAL == getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode);
+ final int targetType = (isGestureNavigateEnabled
+ && android.provider.Flags.a11yStandaloneGestureEnabled()) ? GESTURE : SOFTWARE;
+
if (isGestureNavigateEnabled) {
final TextView promptPrologue = findViewById(R.id.accessibility_button_prompt_prologue);
promptPrologue.setText(isTouchExploreOn
@@ -78,7 +82,7 @@
: R.string.accessibility_gesture_instructional_text);
}
- mTargets.addAll(getTargets(this, SOFTWARE));
+ mTargets.addAll(getTargets(this, targetType));
final GridView gridview = findViewById(R.id.accessibility_button_chooser_grid);
gridview.setAdapter(new ButtonTargetAdapter(mTargets));
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 6256dbc..44c7543 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -18,7 +18,6 @@
import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted;
@@ -213,10 +212,7 @@
final boolean isEditMenuMode =
mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
- final int editDialogTitleId =
- mShortcutType == SOFTWARE
- ? R.string.accessibility_edit_shortcut_menu_button_title
- : R.string.accessibility_edit_shortcut_menu_volume_title;
+ final int editDialogTitleId = R.string.accessibility_edit_shortcut_menu_volume_title;
mMenuDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index ec90dd2..a753110 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -16,6 +16,7 @@
package com.android.internal.accessibility.dialog;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
@@ -197,6 +198,9 @@
@VisibleForTesting
public static boolean isRecognizedShortcutType(@UserShortcutType int shortcutType) {
int mask = SOFTWARE | HARDWARE;
+ if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+ mask = mask | GESTURE;
+ }
return (shortcutType != 0 && (shortcutType & mask) == shortcutType);
}
}
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 8e18f84..9d66461 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -23,6 +23,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
@@ -247,6 +248,8 @@
} else {
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
}
+ case GESTURE:
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
case HARDWARE:
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
case QUICK_SETTINGS:
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 6b0ca9f..48f86ff 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -166,6 +166,8 @@
switch (type) {
case UserShortcutType.SOFTWARE:
return Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
+ case UserShortcutType.GESTURE:
+ return Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS;
case UserShortcutType.HARDWARE:
return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
case UserShortcutType.TRIPLETAP:
@@ -190,6 +192,7 @@
public static int convertToType(String key) {
return switch (key) {
case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> UserShortcutType.SOFTWARE;
+ case Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS -> UserShortcutType.GESTURE;
case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> UserShortcutType.QUICK_SETTINGS;
case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> UserShortcutType.HARDWARE;
case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED ->
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 13014bf..0118c05 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -39,7 +39,9 @@
* Methods in this class are stubs, that are replaced by optimised versions by the ProtoLogTool
* during build.
*/
+// LINT.IfChange
public class ProtoLog {
+// LINT.ThenChange(frameworks/base/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt)
// Needs to be set directly otherwise the protologtool tries to transform the method call
public static boolean REQUIRE_PROTOLOGTOOL = true;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4883827..ea993ee 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2184,6 +2184,344 @@
<enum name="KEYCODE_DEMO_APP_2" value="302" />
<enum name="KEYCODE_DEMO_APP_3" value="303" />
<enum name="KEYCODE_DEMO_APP_4" value="304" />
+ <enum name="KEYCODE_KEYBOARD_BACKLIGHT_DOWN" value="305" />
+ <enum name="KEYCODE_KEYBOARD_BACKLIGHT_UP" value="306" />
+ <enum name="KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE" value="307" />
+ <enum name="KEYCODE_STYLUS_BUTTON_PRIMARY" value="308" />
+ <enum name="KEYCODE_STYLUS_BUTTON_SECONDARY" value="309" />
+ <enum name="KEYCODE_STYLUS_BUTTON_TERTIARY" value="310" />
+ <enum name="KEYCODE_STYLUS_BUTTON_TAIL" value="311" />
+ <enum name="KEYCODE_RECENT_APPS" value="312" />
+ <enum name="KEYCODE_MACRO_1" value="313" />
+ <enum name="KEYCODE_MACRO_2" value="314" />
+ <enum name="KEYCODE_MACRO_3" value="315" />
+ <enum name="KEYCODE_MACRO_4" value="316" />
+ <enum name="KEYCODE_EMOJI_PICKER" value="317" />
+ <enum name="KEYCODE_SCREENSHOT" value="318" />
+ </attr>
+
+ <!-- @hide same as keycode enum defined above, but to be used to define keycode output.
+ (redefining it to allow keycode and outKeycode to be part of same styleable attribute) -->
+ <attr name="outKeycode">
+ <enum name="KEYCODE_UNKNOWN" value="0" />
+ <enum name="KEYCODE_SOFT_LEFT" value="1" />
+ <enum name="KEYCODE_SOFT_RIGHT" value="2" />
+ <enum name="KEYCODE_HOME" value="3" />
+ <enum name="KEYCODE_BACK" value="4" />
+ <enum name="KEYCODE_CALL" value="5" />
+ <enum name="KEYCODE_ENDCALL" value="6" />
+ <enum name="KEYCODE_0" value="7" />
+ <enum name="KEYCODE_1" value="8" />
+ <enum name="KEYCODE_2" value="9" />
+ <enum name="KEYCODE_3" value="10" />
+ <enum name="KEYCODE_4" value="11" />
+ <enum name="KEYCODE_5" value="12" />
+ <enum name="KEYCODE_6" value="13" />
+ <enum name="KEYCODE_7" value="14" />
+ <enum name="KEYCODE_8" value="15" />
+ <enum name="KEYCODE_9" value="16" />
+ <enum name="KEYCODE_STAR" value="17" />
+ <enum name="KEYCODE_POUND" value="18" />
+ <enum name="KEYCODE_DPAD_UP" value="19" />
+ <enum name="KEYCODE_DPAD_DOWN" value="20" />
+ <enum name="KEYCODE_DPAD_LEFT" value="21" />
+ <enum name="KEYCODE_DPAD_RIGHT" value="22" />
+ <enum name="KEYCODE_DPAD_CENTER" value="23" />
+ <enum name="KEYCODE_VOLUME_UP" value="24" />
+ <enum name="KEYCODE_VOLUME_DOWN" value="25" />
+ <enum name="KEYCODE_POWER" value="26" />
+ <enum name="KEYCODE_CAMERA" value="27" />
+ <enum name="KEYCODE_CLEAR" value="28" />
+ <enum name="KEYCODE_A" value="29" />
+ <enum name="KEYCODE_B" value="30" />
+ <enum name="KEYCODE_C" value="31" />
+ <enum name="KEYCODE_D" value="32" />
+ <enum name="KEYCODE_E" value="33" />
+ <enum name="KEYCODE_F" value="34" />
+ <enum name="KEYCODE_G" value="35" />
+ <enum name="KEYCODE_H" value="36" />
+ <enum name="KEYCODE_I" value="37" />
+ <enum name="KEYCODE_J" value="38" />
+ <enum name="KEYCODE_K" value="39" />
+ <enum name="KEYCODE_L" value="40" />
+ <enum name="KEYCODE_M" value="41" />
+ <enum name="KEYCODE_N" value="42" />
+ <enum name="KEYCODE_O" value="43" />
+ <enum name="KEYCODE_P" value="44" />
+ <enum name="KEYCODE_Q" value="45" />
+ <enum name="KEYCODE_R" value="46" />
+ <enum name="KEYCODE_S" value="47" />
+ <enum name="KEYCODE_T" value="48" />
+ <enum name="KEYCODE_U" value="49" />
+ <enum name="KEYCODE_V" value="50" />
+ <enum name="KEYCODE_W" value="51" />
+ <enum name="KEYCODE_X" value="52" />
+ <enum name="KEYCODE_Y" value="53" />
+ <enum name="KEYCODE_Z" value="54" />
+ <enum name="KEYCODE_COMMA" value="55" />
+ <enum name="KEYCODE_PERIOD" value="56" />
+ <enum name="KEYCODE_ALT_LEFT" value="57" />
+ <enum name="KEYCODE_ALT_RIGHT" value="58" />
+ <enum name="KEYCODE_SHIFT_LEFT" value="59" />
+ <enum name="KEYCODE_SHIFT_RIGHT" value="60" />
+ <enum name="KEYCODE_TAB" value="61" />
+ <enum name="KEYCODE_SPACE" value="62" />
+ <enum name="KEYCODE_SYM" value="63" />
+ <enum name="KEYCODE_EXPLORER" value="64" />
+ <enum name="KEYCODE_ENVELOPE" value="65" />
+ <enum name="KEYCODE_ENTER" value="66" />
+ <enum name="KEYCODE_DEL" value="67" />
+ <enum name="KEYCODE_GRAVE" value="68" />
+ <enum name="KEYCODE_MINUS" value="69" />
+ <enum name="KEYCODE_EQUALS" value="70" />
+ <enum name="KEYCODE_LEFT_BRACKET" value="71" />
+ <enum name="KEYCODE_RIGHT_BRACKET" value="72" />
+ <enum name="KEYCODE_BACKSLASH" value="73" />
+ <enum name="KEYCODE_SEMICOLON" value="74" />
+ <enum name="KEYCODE_APOSTROPHE" value="75" />
+ <enum name="KEYCODE_SLASH" value="76" />
+ <enum name="KEYCODE_AT" value="77" />
+ <enum name="KEYCODE_NUM" value="78" />
+ <enum name="KEYCODE_HEADSETHOOK" value="79" />
+ <enum name="KEYCODE_FOCUS" value="80" />
+ <enum name="KEYCODE_PLUS" value="81" />
+ <enum name="KEYCODE_MENU" value="82" />
+ <enum name="KEYCODE_NOTIFICATION" value="83" />
+ <enum name="KEYCODE_SEARCH" value="84" />
+ <enum name="KEYCODE_MEDIA_PLAY_PAUSE" value="85" />
+ <enum name="KEYCODE_MEDIA_STOP" value="86" />
+ <enum name="KEYCODE_MEDIA_NEXT" value="87" />
+ <enum name="KEYCODE_MEDIA_PREVIOUS" value="88" />
+ <enum name="KEYCODE_MEDIA_REWIND" value="89" />
+ <enum name="KEYCODE_MEDIA_FAST_FORWARD" value="90" />
+ <enum name="KEYCODE_MUTE" value="91" />
+ <enum name="KEYCODE_PAGE_UP" value="92" />
+ <enum name="KEYCODE_PAGE_DOWN" value="93" />
+ <enum name="KEYCODE_PICTSYMBOLS" value="94" />
+ <enum name="KEYCODE_SWITCH_CHARSET" value="95" />
+ <enum name="KEYCODE_BUTTON_A" value="96" />
+ <enum name="KEYCODE_BUTTON_B" value="97" />
+ <enum name="KEYCODE_BUTTON_C" value="98" />
+ <enum name="KEYCODE_BUTTON_X" value="99" />
+ <enum name="KEYCODE_BUTTON_Y" value="100" />
+ <enum name="KEYCODE_BUTTON_Z" value="101" />
+ <enum name="KEYCODE_BUTTON_L1" value="102" />
+ <enum name="KEYCODE_BUTTON_R1" value="103" />
+ <enum name="KEYCODE_BUTTON_L2" value="104" />
+ <enum name="KEYCODE_BUTTON_R2" value="105" />
+ <enum name="KEYCODE_BUTTON_THUMBL" value="106" />
+ <enum name="KEYCODE_BUTTON_THUMBR" value="107" />
+ <enum name="KEYCODE_BUTTON_START" value="108" />
+ <enum name="KEYCODE_BUTTON_SELECT" value="109" />
+ <enum name="KEYCODE_BUTTON_MODE" value="110" />
+ <enum name="KEYCODE_ESCAPE" value="111" />
+ <enum name="KEYCODE_FORWARD_DEL" value="112" />
+ <enum name="KEYCODE_CTRL_LEFT" value="113" />
+ <enum name="KEYCODE_CTRL_RIGHT" value="114" />
+ <enum name="KEYCODE_CAPS_LOCK" value="115" />
+ <enum name="KEYCODE_SCROLL_LOCK" value="116" />
+ <enum name="KEYCODE_META_LEFT" value="117" />
+ <enum name="KEYCODE_META_RIGHT" value="118" />
+ <enum name="KEYCODE_FUNCTION" value="119" />
+ <enum name="KEYCODE_SYSRQ" value="120" />
+ <enum name="KEYCODE_BREAK" value="121" />
+ <enum name="KEYCODE_MOVE_HOME" value="122" />
+ <enum name="KEYCODE_MOVE_END" value="123" />
+ <enum name="KEYCODE_INSERT" value="124" />
+ <enum name="KEYCODE_FORWARD" value="125" />
+ <enum name="KEYCODE_MEDIA_PLAY" value="126" />
+ <enum name="KEYCODE_MEDIA_PAUSE" value="127" />
+ <enum name="KEYCODE_MEDIA_CLOSE" value="128" />
+ <enum name="KEYCODE_MEDIA_EJECT" value="129" />
+ <enum name="KEYCODE_MEDIA_RECORD" value="130" />
+ <enum name="KEYCODE_F1" value="131" />
+ <enum name="KEYCODE_F2" value="132" />
+ <enum name="KEYCODE_F3" value="133" />
+ <enum name="KEYCODE_F4" value="134" />
+ <enum name="KEYCODE_F5" value="135" />
+ <enum name="KEYCODE_F6" value="136" />
+ <enum name="KEYCODE_F7" value="137" />
+ <enum name="KEYCODE_F8" value="138" />
+ <enum name="KEYCODE_F9" value="139" />
+ <enum name="KEYCODE_F10" value="140" />
+ <enum name="KEYCODE_F11" value="141" />
+ <enum name="KEYCODE_F12" value="142" />
+ <enum name="KEYCODE_NUM_LOCK" value="143" />
+ <enum name="KEYCODE_NUMPAD_0" value="144" />
+ <enum name="KEYCODE_NUMPAD_1" value="145" />
+ <enum name="KEYCODE_NUMPAD_2" value="146" />
+ <enum name="KEYCODE_NUMPAD_3" value="147" />
+ <enum name="KEYCODE_NUMPAD_4" value="148" />
+ <enum name="KEYCODE_NUMPAD_5" value="149" />
+ <enum name="KEYCODE_NUMPAD_6" value="150" />
+ <enum name="KEYCODE_NUMPAD_7" value="151" />
+ <enum name="KEYCODE_NUMPAD_8" value="152" />
+ <enum name="KEYCODE_NUMPAD_9" value="153" />
+ <enum name="KEYCODE_NUMPAD_DIVIDE" value="154" />
+ <enum name="KEYCODE_NUMPAD_MULTIPLY" value="155" />
+ <enum name="KEYCODE_NUMPAD_SUBTRACT" value="156" />
+ <enum name="KEYCODE_NUMPAD_ADD" value="157" />
+ <enum name="KEYCODE_NUMPAD_DOT" value="158" />
+ <enum name="KEYCODE_NUMPAD_COMMA" value="159" />
+ <enum name="KEYCODE_NUMPAD_ENTER" value="160" />
+ <enum name="KEYCODE_NUMPAD_EQUALS" value="161" />
+ <enum name="KEYCODE_NUMPAD_LEFT_PAREN" value="162" />
+ <enum name="KEYCODE_NUMPAD_RIGHT_PAREN" value="163" />
+ <enum name="KEYCODE_VOLUME_MUTE" value="164" />
+ <enum name="KEYCODE_INFO" value="165" />
+ <enum name="KEYCODE_CHANNEL_UP" value="166" />
+ <enum name="KEYCODE_CHANNEL_DOWN" value="167" />
+ <enum name="KEYCODE_ZOOM_IN" value="168" />
+ <enum name="KEYCODE_ZOOM_OUT" value="169" />
+ <enum name="KEYCODE_TV" value="170" />
+ <enum name="KEYCODE_WINDOW" value="171" />
+ <enum name="KEYCODE_GUIDE" value="172" />
+ <enum name="KEYCODE_DVR" value="173" />
+ <enum name="KEYCODE_BOOKMARK" value="174" />
+ <enum name="KEYCODE_CAPTIONS" value="175" />
+ <enum name="KEYCODE_SETTINGS" value="176" />
+ <enum name="KEYCODE_TV_POWER" value="177" />
+ <enum name="KEYCODE_TV_INPUT" value="178" />
+ <enum name="KEYCODE_STB_POWER" value="179" />
+ <enum name="KEYCODE_STB_INPUT" value="180" />
+ <enum name="KEYCODE_AVR_POWER" value="181" />
+ <enum name="KEYCODE_AVR_INPUT" value="182" />
+ <enum name="KEYCODE_PROG_GRED" value="183" />
+ <enum name="KEYCODE_PROG_GREEN" value="184" />
+ <enum name="KEYCODE_PROG_YELLOW" value="185" />
+ <enum name="KEYCODE_PROG_BLUE" value="186" />
+ <enum name="KEYCODE_APP_SWITCH" value="187" />
+ <enum name="KEYCODE_BUTTON_1" value="188" />
+ <enum name="KEYCODE_BUTTON_2" value="189" />
+ <enum name="KEYCODE_BUTTON_3" value="190" />
+ <enum name="KEYCODE_BUTTON_4" value="191" />
+ <enum name="KEYCODE_BUTTON_5" value="192" />
+ <enum name="KEYCODE_BUTTON_6" value="193" />
+ <enum name="KEYCODE_BUTTON_7" value="194" />
+ <enum name="KEYCODE_BUTTON_8" value="195" />
+ <enum name="KEYCODE_BUTTON_9" value="196" />
+ <enum name="KEYCODE_BUTTON_10" value="197" />
+ <enum name="KEYCODE_BUTTON_11" value="198" />
+ <enum name="KEYCODE_BUTTON_12" value="199" />
+ <enum name="KEYCODE_BUTTON_13" value="200" />
+ <enum name="KEYCODE_BUTTON_14" value="201" />
+ <enum name="KEYCODE_BUTTON_15" value="202" />
+ <enum name="KEYCODE_BUTTON_16" value="203" />
+ <enum name="KEYCODE_LANGUAGE_SWITCH" value="204" />
+ <enum name="KEYCODE_MANNER_MODE" value="205" />
+ <enum name="KEYCODE_3D_MODE" value="206" />
+ <enum name="KEYCODE_CONTACTS" value="207" />
+ <enum name="KEYCODE_CALENDAR" value="208" />
+ <enum name="KEYCODE_MUSIC" value="209" />
+ <enum name="KEYCODE_CALCULATOR" value="210" />
+ <enum name="KEYCODE_ZENKAKU_HANKAKU" value="211" />
+ <enum name="KEYCODE_EISU" value="212" />
+ <enum name="KEYCODE_MUHENKAN" value="213" />
+ <enum name="KEYCODE_HENKAN" value="214" />
+ <enum name="KEYCODE_KATAKANA_HIRAGANA" value="215" />
+ <enum name="KEYCODE_YEN" value="216" />
+ <enum name="KEYCODE_RO" value="217" />
+ <enum name="KEYCODE_KANA" value="218" />
+ <enum name="KEYCODE_ASSIST" value="219" />
+ <enum name="KEYCODE_BRIGHTNESS_DOWN" value="220" />
+ <enum name="KEYCODE_BRIGHTNESS_UP" value="221" />
+ <enum name="KEYCODE_MEDIA_AUDIO_TRACK" value="222" />
+ <enum name="KEYCODE_MEDIA_SLEEP" value="223" />
+ <enum name="KEYCODE_MEDIA_WAKEUP" value="224" />
+ <enum name="KEYCODE_PAIRING" value="225" />
+ <enum name="KEYCODE_MEDIA_TOP_MENU" value="226" />
+ <enum name="KEYCODE_11" value="227" />
+ <enum name="KEYCODE_12" value="228" />
+ <enum name="KEYCODE_LAST_CHANNEL" value="229" />
+ <enum name="KEYCODE_TV_DATA_SERVICE" value="230" />
+ <enum name="KEYCODE_VOICE_ASSIST" value="231" />
+ <enum name="KEYCODE_TV_RADIO_SERVICE" value="232" />
+ <enum name="KEYCODE_TV_TELETEXT" value="233" />
+ <enum name="KEYCODE_TV_NUMBER_ENTRY" value="234" />
+ <enum name="KEYCODE_TV_TERRESTRIAL_ANALOG" value="235" />
+ <enum name="KEYCODE_TV_TERRESTRIAL_DIGITAL" value="236" />
+ <enum name="KEYCODE_TV_SATELLITE" value="237" />
+ <enum name="KEYCODE_TV_SATELLITE_BS" value="238" />
+ <enum name="KEYCODE_TV_SATELLITE_CS" value="239" />
+ <enum name="KEYCODE_TV_SATELLITE_SERVICE" value="240" />
+ <enum name="KEYCODE_TV_NETWORK" value="241" />
+ <enum name="KEYCODE_TV_ANTENNA_CABLE" value="242" />
+ <enum name="KEYCODE_TV_INPUT_HDMI_1" value="243" />
+ <enum name="KEYCODE_TV_INPUT_HDMI_2" value="244" />
+ <enum name="KEYCODE_TV_INPUT_HDMI_3" value="245" />
+ <enum name="KEYCODE_TV_INPUT_HDMI_4" value="246" />
+ <enum name="KEYCODE_TV_INPUT_COMPOSITE_1" value="247" />
+ <enum name="KEYCODE_TV_INPUT_COMPOSITE_2" value="248" />
+ <enum name="KEYCODE_TV_INPUT_COMPONENT_1" value="249" />
+ <enum name="KEYCODE_TV_INPUT_COMPONENT_2" value="250" />
+ <enum name="KEYCODE_TV_INPUT_VGA_1" value="251" />
+ <enum name="KEYCODE_TV_AUDIO_DESCRIPTION" value="252" />
+ <enum name="KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP" value="253" />
+ <enum name="KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN" value="254" />
+ <enum name="KEYCODE_TV_ZOOM_MODE" value="255" />
+ <enum name="KEYCODE_TV_CONTENTS_MENU" value="256" />
+ <enum name="KEYCODE_TV_MEDIA_CONTEXT_MENU" value="257" />
+ <enum name="KEYCODE_TV_TIMER_PROGRAMMING" value="258" />
+ <enum name="KEYCODE_HELP" value="259" />
+ <enum name="KEYCODE_NAVIGATE_PREVIOUS" value="260" />
+ <enum name="KEYCODE_NAVIGATE_NEXT" value="261" />
+ <enum name="KEYCODE_NAVIGATE_IN" value="262" />
+ <enum name="KEYCODE_NAVIGATE_OUT" value="263" />
+ <enum name="KEYCODE_STEM_PRIMARY" value="264" />
+ <enum name="KEYCODE_STEM_1" value="265" />
+ <enum name="KEYCODE_STEM_2" value="266" />
+ <enum name="KEYCODE_STEM_3" value="267" />
+ <enum name="KEYCODE_DPAD_UP_LEFT" value="268" />
+ <enum name="KEYCODE_DPAD_DOWN_LEFT" value="269" />
+ <enum name="KEYCODE_DPAD_UP_RIGHT" value="270" />
+ <enum name="KEYCODE_DPAD_DOWN_RIGHT" value="271" />
+ <enum name="KEYCODE_MEDIA_SKIP_FORWARD" value="272" />
+ <enum name="KEYCODE_MEDIA_SKIP_BACKWARD" value="273" />
+ <enum name="KEYCODE_MEDIA_STEP_FORWARD" value="274" />
+ <enum name="KEYCODE_MEDIA_STEP_BACKWARD" value="275" />
+ <enum name="KEYCODE_SOFT_SLEEP" value="276" />
+ <enum name="KEYCODE_CUT" value="277" />
+ <enum name="KEYCODE_COPY" value="278" />
+ <enum name="KEYCODE_PASTE" value="279" />
+ <enum name="KEYCODE_SYSTEM_NAVIGATION_UP" value="280" />
+ <enum name="KEYCODE_SYSTEM_NAVIGATION_DOWN" value="281" />
+ <enum name="KEYCODE_SYSTEM_NAVIGATION_LEFT" value="282" />
+ <enum name="KEYCODE_SYSTEM_NAVIGATION_RIGHT" value="283" />
+ <enum name="KEYCODE_ALL_APPS" value="284" />
+ <enum name="KEYCODE_REFRESH" value="285" />
+ <enum name="KEYCODE_THUMBS_UP" value="286" />
+ <enum name="KEYCODE_THUMBS_DOWN" value="287" />
+ <enum name="KEYCODE_PROFILE_SWITCH" value="288" />
+ <enum name="KEYCODE_VIDEO_APP_1" value="289" />
+ <enum name="KEYCODE_VIDEO_APP_2" value="290" />
+ <enum name="KEYCODE_VIDEO_APP_3" value="291" />
+ <enum name="KEYCODE_VIDEO_APP_4" value="292" />
+ <enum name="KEYCODE_VIDEO_APP_5" value="293" />
+ <enum name="KEYCODE_VIDEO_APP_6" value="294" />
+ <enum name="KEYCODE_VIDEO_APP_7" value="295" />
+ <enum name="KEYCODE_VIDEO_APP_8" value="296" />
+ <enum name="KEYCODE_FEATURED_APP_1" value="297" />
+ <enum name="KEYCODE_FEATURED_APP_2" value="298" />
+ <enum name="KEYCODE_FEATURED_APP_3" value="299" />
+ <enum name="KEYCODE_FEATURED_APP_4" value="300" />
+ <enum name="KEYCODE_DEMO_APP_1" value="301" />
+ <enum name="KEYCODE_DEMO_APP_2" value="302" />
+ <enum name="KEYCODE_DEMO_APP_3" value="303" />
+ <enum name="KEYCODE_DEMO_APP_4" value="304" />
+ <enum name="KEYCODE_KEYBOARD_BACKLIGHT_DOWN" value="305" />
+ <enum name="KEYCODE_KEYBOARD_BACKLIGHT_UP" value="306" />
+ <enum name="KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE" value="307" />
+ <enum name="KEYCODE_STYLUS_BUTTON_PRIMARY" value="308" />
+ <enum name="KEYCODE_STYLUS_BUTTON_SECONDARY" value="309" />
+ <enum name="KEYCODE_STYLUS_BUTTON_TERTIARY" value="310" />
+ <enum name="KEYCODE_STYLUS_BUTTON_TAIL" value="311" />
+ <enum name="KEYCODE_RECENT_APPS" value="312" />
+ <enum name="KEYCODE_MACRO_1" value="313" />
+ <enum name="KEYCODE_MACRO_2" value="314" />
+ <enum name="KEYCODE_MACRO_3" value="315" />
+ <enum name="KEYCODE_MACRO_4" value="316" />
+ <enum name="KEYCODE_EMOJI_PICKER" value="317" />
+ <enum name="KEYCODE_SCREENSHOT" value="318" />
</attr>
<!-- ***************************************************************** -->
@@ -9844,6 +10182,63 @@
<attr name="productId" format="integer" />
</declare-styleable>
+ <!-- @hide -->
+ <declare-styleable name="KeyboardGlyphMap">
+ <attr name="glyphMap" format="reference" />
+ <attr name="vendorId" format="integer" />
+ <attr name="productId" format="integer" />
+ </declare-styleable>
+
+ <!-- @hide -->
+ <declare-styleable name="KeyGlyph">
+ <attr name="keycode" />
+ <attr name="glyphDrawable" format="reference" />
+ </declare-styleable>
+
+ <!-- @hide -->
+ <declare-styleable name="ModifierGlyph">
+ <!-- The values are taken from public constants for modifier state defined in
+ {@see KeyEvent.java}. Here we explicitly allow only one modifier bit as value, since
+ this represents the modifier key -->
+ <attr name="modifier">
+ <enum name="META" value="0x10000" />
+ <enum name="CTRL" value="0x1000" />
+ <enum name="ALT" value="0x02" />
+ <enum name="SHIFT" value="0x1" />
+ <enum name="SYM" value="0x4" />
+ <enum name="FUNCTION" value="0x8" />
+ <enum name="CAPS_LOCK" value="0x100000" />
+ <enum name="NUM_LOCK" value="0x200000" />
+ <enum name="SCROLL_LOCK" value="0x400000" />
+ </attr>
+ <attr name="glyphDrawable" format="reference" />
+ </declare-styleable>
+
+ <!-- @hide -->
+ <declare-styleable name="HardwareDefinedShortcut">
+ <attr name="keycode" />
+ <!-- The values are taken from public constants for modifier state defined in
+ {@see KeyEvent.java}. Here we allow multiple modifier flags as value, since this
+ represents the modifier state -->
+ <attr name="modifierState">
+ <flag name="META" value="0x10000" />
+ <flag name="CTRL" value="0x1000" />
+ <flag name="ALT" value="0x02" />
+ <flag name="SHIFT" value="0x1" />
+ <flag name="SYM" value="0x4" />
+ <flag name="FUNCTION" value="0x8" />
+ <flag name="CAPS_LOCK" value="0x100000" />
+ <flag name="NUM_LOCK" value="0x200000" />
+ <flag name="SCROLL_LOCK" value="0x400000" />
+ </attr>
+ <attr name="outKeycode" />
+ </declare-styleable>
+
+ <!-- @hide -->
+ <declare-styleable name="FunctionRowKey">
+ <attr name="keycode" />
+ </declare-styleable>
+
<declare-styleable name="MediaRouteButton">
<!-- This drawable is a state list where the "activated" state
indicates active media routing. Non-activated indicates
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index e983427..61c7a8c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -424,4 +424,11 @@
<bool name="config_dropboxmanager_persistent_logging_enabled">false</bool>
<java-symbol type="bool" name="config_dropboxmanager_persistent_logging_enabled" />
+ <!-- Telephony satellite gateway class name for handling carrier roaming to satellite is using ESOS messaging. -->
+ <string name="config_satellite_carrier_roaming_esos_provisioned_class" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_class" />
+
+ <!-- Telephony satellite gateway intent for handling carrier roaming to satellite is using ESOS messaging. -->
+ <string name="config_satellite_carrier_roaming_esos_provisioned_intent_action" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_intent_action" />
</resources>
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
index 389b677..f01ac6f 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
@@ -16,15 +16,22 @@
package com.android.internal.accessibility.dialog;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Flags;
+
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.accessibility.common.ShortcutConstants;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,9 +39,13 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityTargetTest {
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int[] EXPECTED_TYPES = { HARDWARE, SOFTWARE };
+ private static final int[] EXPECTED_TYPES_GESTURE = { HARDWARE, SOFTWARE, GESTURE };
@Test
+ @DisableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void isRecognizedShortcutType_expectedType_isTrue() {
for (int type : EXPECTED_TYPES) {
assertThat(AccessibilityTarget.isRecognizedShortcutType(type)).isTrue();
@@ -42,6 +53,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void isRecognizedShortcutType_notExpectedType_isFalse() {
for (int type: ShortcutConstants.USER_SHORTCUT_TYPES) {
if (IntStream.of(EXPECTED_TYPES).noneMatch(x -> x == type)) {
@@ -49,4 +61,28 @@
}
}
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+ public void isRecognizedShortcutType_expectedType_gestureIncluded_isTrue() {
+ for (int type : EXPECTED_TYPES_GESTURE) {
+ if (!AccessibilityTarget.isRecognizedShortcutType(type)) {
+ throw new AssertionError(
+ "Shortcut type " + type + " should be recognized");
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+ public void isRecognizedShortcutType_notExpectedType_gestureIncluded_isFalse() {
+ for (int type: ShortcutConstants.USER_SHORTCUT_TYPES) {
+ if (IntStream.of(EXPECTED_TYPES_GESTURE).noneMatch(x -> x == type)) {
+ if (AccessibilityTarget.isRecognizedShortcutType(type)) {
+ throw new AssertionError(
+ "Shortcut type " + type + " should not be recognized");
+ }
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
index 928fce9..8bebc62 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
@@ -20,6 +20,8 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.google.common.truth.Truth.assertThat;
@@ -99,8 +101,7 @@
public void getShortcutTargets_softwareShortcutNoService_emptyResult() {
assertThat(
ShortcutUtils.getShortcutTargetsFromSettings(
- mContext,
- ShortcutConstants.UserShortcutType.SOFTWARE, mDefaultUserId)
+ mContext, SOFTWARE, mDefaultUserId)
).isEmpty();
}
@@ -114,13 +115,21 @@
}
@Test
+ public void getShortcutTargets_gestureShortcutNoService_emptyResult() {
+ assertThat(
+ ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext, GESTURE, mDefaultUserId)
+ ).isEmpty();
+ }
+
+ @Test
public void getShortcutTargets_softwareShortcut1Service_return1Service() {
setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
setupShortcutTargets(TWO_COMPONENTS, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
assertThat(
ShortcutUtils.getShortcutTargetsFromSettings(
- mContext, ShortcutConstants.UserShortcutType.SOFTWARE,
+ mContext, SOFTWARE,
mDefaultUserId)
).containsExactlyElementsIn(ONE_COMPONENT);
}
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 66b47da..a115c65 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -40,6 +40,7 @@
<permission name="android.permission.MASTER_CLEAR"/>
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING" />
+ <permission name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index f9a6caf..612b387 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
@@ -29,6 +30,7 @@
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
+import android.annotation.ColorInt;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -391,13 +393,22 @@
if (splitAttributes == null) {
return TaskFragmentAnimationParams.DEFAULT;
}
+ final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes);
+ TaskFragmentAnimationParams.Builder builder = new TaskFragmentAnimationParams.Builder();
+ if (animationBackgroundColor != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
+ builder.setAnimationBackgroundColor(animationBackgroundColor);
+ }
+ // TODO(b/293658614): Allow setting custom open/close/changeAnimationResId.
+ return builder.build();
+ }
+
+ @ColorInt
+ private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) {
+ int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
- return new TaskFragmentAnimationParams.Builder()
- .setAnimationBackgroundColor(colorBackground.getColor())
- .build();
- } else {
- return TaskFragmentAnimationParams.DEFAULT;
+ animationBackgroundColor = colorBackground.getColor();
}
+ return animationBackgroundColor;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 217b1d3..1bf1259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -19,6 +19,8 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.app.TaskInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
import android.content.pm.ActivityInfo.isFixedOrientationLandscape
import android.content.pm.ActivityInfo.isFixedOrientationPortrait
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
@@ -105,7 +107,7 @@
* Calculates the largest size that can fit in a given area while maintaining a specific aspect
* ratio.
*/
-private fun maximumSizeMaintainingAspectRatio(
+fun maximumSizeMaintainingAspectRatio(
taskInfo: RunningTaskInfo,
targetArea: Size,
aspectRatio: Float
@@ -114,7 +116,8 @@
val targetWidth = targetArea.width
val finalHeight: Int
val finalWidth: Int
- if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) {
+ // Get orientation either through top activity or task's orientation
+ if (taskInfo.hasPortraitTopActivity()) {
val tempWidth = (targetHeight / aspectRatio).toInt()
if (tempWidth <= targetWidth) {
finalHeight = targetHeight
@@ -137,7 +140,7 @@
}
/** Calculates the aspect ratio of an activity from its fullscreen bounds. */
-private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
@@ -171,3 +174,41 @@
desiredSize.height + heightOffset
)
}
+
+/**
+ * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
+ * entire screen, as area can be offset by left and top start.
+ */
+fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart: Int): Rect {
+ val heightOffset = (areaBounds.height() - desiredSize.height) / 2
+ val widthOffset = (areaBounds.width() - desiredSize.width) / 2
+
+ val newLeft = leftStart + widthOffset
+ val newTop = topStart + heightOffset
+ val newRight = newLeft + desiredSize.width
+ val newBottom = newTop + desiredSize.height
+
+ return Rect(newLeft, newTop, newRight, newBottom)
+}
+
+fun TaskInfo.hasPortraitTopActivity(): Boolean {
+ val topActivityScreenOrientation =
+ topActivityInfo?.screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED
+ val appBounds = configuration.windowConfiguration.appBounds
+
+ return when {
+ // First check if activity has portrait screen orientation
+ topActivityScreenOrientation != SCREEN_ORIENTATION_UNSPECIFIED -> {
+ isFixedOrientationPortrait(topActivityScreenOrientation)
+ }
+
+ // Then check if the activity is portrait when letterboxed
+ appCompatTaskInfo.topActivityBoundsLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+
+ // Then check if the activity is portrait
+ appBounds != null -> appBounds.height() > appBounds.width()
+
+ // Otherwise just take the orientation of the task
+ else -> isFixedOrientationPortrait(configuration.orientation)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 5813f85..7cc01d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -36,6 +36,7 @@
import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
+import android.util.Size
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
@@ -416,7 +417,7 @@
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
- moveHomeTaskToFront(wct)
+ moveHomeTask(wct, true /* toTop */)
val taskToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
@@ -649,13 +650,21 @@
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
- val stableBounds = Rect()
- displayLayout.getStableBounds(stableBounds)
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
val destinationBounds = Rect()
- if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
- // The desktop task is currently occupying the whole stable bounds. If the bounds
- // before the task was toggled to stable bounds were saved, toggle the task to those
- // bounds. Otherwise, toggle to the default bounds.
+
+ val isMaximized = if (taskInfo.isResizeable) {
+ currentTaskBounds == stableBounds
+ } else {
+ currentTaskBounds.width() == stableBounds.width()
+ || currentTaskBounds.height() == stableBounds.height()
+ }
+
+ if (isMaximized) {
+ // The desktop task is at the maximized width and/or height of the stable bounds.
+ // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
+ // Otherwise, toggle to the default bounds.
val taskBoundsBeforeMaximize =
desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
if (taskBoundsBeforeMaximize != null) {
@@ -670,9 +679,20 @@
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
// and toggle to the stable bounds.
- val taskBounds = taskInfo.configuration.windowConfiguration.bounds
- desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, taskBounds)
- destinationBounds.set(stableBounds)
+ desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
+
+ if (taskInfo.isResizeable) {
+ // if resizable then expand to entire stable bounds (full display minus insets)
+ destinationBounds.set(stableBounds)
+ } else {
+ // if non-resizable then calculate max bounds according to aspect ratio
+ val activityAspectRatio = calculateAspectRatio(taskInfo)
+ val newSize = maximumSizeMaintainingAspectRatio(taskInfo,
+ Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
+ val newBounds = centerInArea(
+ newSize, stableBounds, stableBounds.left, stableBounds.top)
+ destinationBounds.set(newBounds)
+ }
}
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
@@ -775,7 +795,7 @@
addWallpaperActivity(wct)
} else {
// Move home to front
- moveHomeTaskToFront(wct)
+ moveHomeTask(wct, true /* toTop */)
}
val nonMinimizedTasksOrderedFrontToBack =
@@ -801,11 +821,11 @@
return taskToMinimize
}
- private fun moveHomeTaskToFront(wct: WindowContainerTransaction) {
+ private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
shellTaskOrganizer
.getRunningTasks(context.displayId)
.firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
- ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
+ ?.let { homeTask -> wct.reorder(homeTask.getToken(), toTop /* onTop */) }
}
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
@@ -1020,6 +1040,9 @@
)
return WindowContainerTransaction().also { wct ->
addMoveToDesktopChanges(wct, task)
+ // In some launches home task is moved behind new task being launched. Make sure
+ // that's not the case for launches in desktop.
+ moveHomeTask(wct, false /* toTop */)
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index dc449d1..a8346a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -625,6 +626,14 @@
}
} else {
adjustedSourceRectHint.set(sourceRectHint);
+ if (isInPipDirection(direction)
+ && rotationDelta == ROTATION_0
+ && taskInfo.displayCutoutInsets != null) {
+ // TODO: this is to special case the issues on Foldable device
+ // with display cutout. This aligns with what's in SwipePipToHomeAnimator.
+ adjustedSourceRectHint.offset(taskInfo.displayCutoutInsets.left,
+ taskInfo.displayCutoutInsets.top);
+ }
}
final Rect sourceHintRectInsets = new Rect();
if (!adjustedSourceRectHint.isEmpty()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 789f706..6a6e2ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -636,6 +636,13 @@
return;
}
+ // bail early if leash is null
+ if (mLeash == null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "exitPip: leash is null");
+ return;
+ }
+
final Rect destinationBounds = new Rect(getExitDestinationBounds());
final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index ccbe94c..8d36db9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -127,8 +127,10 @@
/**
* Called when the Shell wants to start resizing Pip transition/animation.
+ *
+ * @param duration the suggested duration for resize animation.
*/
- public void startResizeTransition(WindowContainerTransaction wct) {
+ public void startResizeTransition(WindowContainerTransaction wct, int duration) {
// Default implementation does nothing.
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 5c561fe..88f9e4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -47,9 +47,17 @@
@Nullable
private Runnable mAnimationEndCallback;
private RectEvaluator mRectEvaluator;
+
+ // Bounds relative to which scaling/cropping must be done.
private final Rect mBaseBounds = new Rect();
+
+ // Bounds to animate from.
private final Rect mStartBounds = new Rect();
+
+ // Target bounds.
private final Rect mEndBounds = new Rect();
+
+ // Bounds updated by the evaluator as animator is running.
private final Rect mAnimatedRect = new Rect();
private final float mDelta;
@@ -84,7 +92,6 @@
addListener(this);
addUpdateListener(this);
setEvaluator(mRectEvaluator);
- // TODO: change this
setDuration(duration);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 68202ef..06adad6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -352,6 +352,7 @@
mPipBoundsAlgorithm.dump(pw, innerPrefix);
mPipBoundsState.dump(pw, innerPrefix);
mPipDisplayLayoutState.dump(pw, innerPrefix);
+ mPipTransitionState.dump(pw, innerPrefix);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index cec2469..e277a8d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
@@ -25,6 +26,7 @@
import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS;
import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +38,7 @@
import android.view.SurfaceControl;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -45,6 +48,7 @@
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
@@ -62,6 +66,7 @@
PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = "PipMotionHelper";
private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
+ private static final String ANIMATING_BOUNDS_CHANGE = "animating_bounds_change";
private static final boolean DEBUG = false;
private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
@@ -113,7 +118,7 @@
/** SpringConfig to use for fling-then-spring animations. */
private final PhysicsAnimator.SpringConfig mSpringConfig =
- new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY);
+ new PhysicsAnimator.SpringConfig(300f, DAMPING_RATIO_LOW_BOUNCY);
/** SpringConfig used for animating into the dismiss region, matches the one in
* {@link MagnetizedObject}. */
@@ -152,9 +157,16 @@
private boolean mDismissalPending = false;
/**
- * Set to true if bounds change transition has been scheduled from PipMotionHelper.
+ * Set to true if bounds change transition has been scheduled from PipMotionHelper
+ * after animating is over.
*/
- private boolean mWaitingForBoundsChangeTransition = false;
+ private boolean mWaitingForFlingTransition = false;
+
+ /**
+ * Set to true if bounds change transition has been scheduled from PipMotionHelper,
+ * and if the animation is supposed to run while transition is playing.
+ */
+ private boolean mWaitingToPlayBoundsChangeTransition = false;
/**
* Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
@@ -634,6 +646,9 @@
// The physics animation ended, though we may not necessarily be done animating, such as
// when we're still dragging after moving out of the magnetic target.
if (!mDismissalPending && !mSpringingToTouch && !mMagnetizedPip.getObjectStuckToTarget()) {
+ // Update the earlier estimate on bounds we are animating towards, since physics
+ // animator is non-deterministic.
+ setAnimatingToBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
// do not schedule resize if PiP is dismissing, which may cause app re-open to
// mBounds instead of its normal bounds.
Bundle extra = new Bundle();
@@ -673,6 +688,11 @@
* Directly resizes the PiP to the given {@param bounds}.
*/
private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+ if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ // Do not carry out any resizing if we are dragging or physics animator is running.
+ return;
+ }
+
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: resizeAndAnimatePipUnchecked: toBounds=%s"
@@ -682,10 +702,11 @@
// Intentionally resize here even if the current bounds match the destination bounds.
// This is so all the proper callbacks are performed.
-
- // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration,
- // TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */);
- // setAnimatingToBounds(toBounds);
+ setAnimatingToBounds(toBounds);
+ Bundle extra = new Bundle();
+ extra.putBoolean(ANIMATING_BOUNDS_CHANGE, true);
+ extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, duration);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
}
@Override
@@ -694,7 +715,11 @@
@Nullable Bundle extra) {
switch (newState) {
case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
- if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break;
+ mWaitingForFlingTransition = extra.getBoolean(FLING_BOUNDS_CHANGE);
+ mWaitingToPlayBoundsChangeTransition = extra.getBoolean(ANIMATING_BOUNDS_CHANGE);
+ if (!mWaitingForFlingTransition && !mWaitingToPlayBoundsChangeTransition) {
+ break;
+ }
if (mPipBoundsState.getBounds().equals(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion())) {
@@ -709,30 +734,30 @@
break;
}
- // If touch is turned off and we are in a fling animation, schedule a transition.
- mWaitingForBoundsChangeTransition = true;
+ // Delay config until the end, if we are animating after scheduling the transition.
mPipScheduler.scheduleAnimateResizePip(
- mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ mPipBoundsState.getMotionBoundsState().getAnimatingToBounds(),
+ mWaitingToPlayBoundsChangeTransition,
+ extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION));
break;
case PipTransitionState.CHANGING_PIP_BOUNDS:
- if (!mWaitingForBoundsChangeTransition) break;
-
- // If bounds change transition was scheduled from this class, handle leash updates.
- mWaitingForBoundsChangeTransition = false;
SurfaceControl.Transaction startTx = extra.getParcelable(
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishTx = extra.getParcelable(
+ PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
Rect destinationBounds = extra.getParcelable(
PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
- startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
- destinationBounds.left, destinationBounds.top);
- startTx.apply();
+ final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
- // All motion operations have actually finished, so make bounds cache updates.
- settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
- cleanUpHighPerfSessionMaybe();
-
- // Signal that the transition is done - should update transition state by default.
- mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+ if (mWaitingForFlingTransition) {
+ mWaitingForFlingTransition = false;
+ handleFlingTransition(startTx, finishTx, destinationBounds);
+ } else if (mWaitingToPlayBoundsChangeTransition) {
+ mWaitingToPlayBoundsChangeTransition = false;
+ startResizeAnimation(startTx, finishTx, destinationBounds, duration);
+ }
break;
case PipTransitionState.EXITING_PIP:
// We need to force finish any local animators if about to leave PiP, to avoid
@@ -740,9 +765,46 @@
if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
cancelPhysicsAnimation();
settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+ break;
}
}
+ private void handleFlingTransition(SurfaceControl.Transaction startTx,
+ SurfaceControl.Transaction finishTx, Rect destinationBounds) {
+ startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+ destinationBounds.left, destinationBounds.top);
+ startTx.apply();
+
+ // All motion operations have actually finished, so make bounds cache updates.
+ settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+ cleanUpHighPerfSessionMaybe();
+
+ // Signal that the transition is done - should update transition state by default.
+ mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+ }
+
+ private void startResizeAnimation(SurfaceControl.Transaction startTx,
+ SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) {
+ SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ Preconditions.checkState(pipLeash != null,
+ "No leash cached by mPipTransitionState=" + mPipTransitionState);
+
+ startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
+ mPipBoundsState.getBounds().height());
+
+ PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
+ startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
+ destinationBounds, duration, 0f /* angle */);
+ animator.setAnimationEndCallback(() -> {
+ mPipBoundsState.setBounds(destinationBounds);
+ // All motion operations have actually finished, so make bounds cache updates.
+ cleanUpHighPerfSessionMaybe();
+ // Signal that we are done with resize transition
+ mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+ });
+ animator.start();
+ }
+
private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) {
if (!animatingAfter) {
// The physics animation ended, though we may not necessarily be done animating, such as
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 33e80bd..5b0ca18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
import android.annotation.Nullable;
import android.content.Context;
@@ -535,7 +536,8 @@
mWaitingForBoundsChangeTransition = true;
// Schedule PiP resize transition, but delay any config updates until very end.
- mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds, true /* configAtEnd */);
+ mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds,
+ true /* configAtEnd */, PINCH_RESIZE_SNAP_DURATION);
break;
case PipTransitionState.CHANGING_PIP_BOUNDS:
if (!mWaitingForBoundsChangeTransition) break;
@@ -550,12 +552,15 @@
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishTx = extra.getParcelable(
PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
+ final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+
startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
mPipBoundsState.getBounds().height());
PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
startTx, finishTx, mPipBoundsState.getBounds(), mStartBoundsAfterRelease,
- mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, mAngle);
+ mLastResizeBounds, duration, mAngle);
animator.setAnimationEndCallback(() -> {
// All motion operations have actually finished, so make bounds cache updates.
mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 4f62192..ac670cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -162,6 +162,18 @@
* @param configAtEnd true if we are delaying config updates until the transition ends.
*/
public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd) {
+ scheduleAnimateResizePip(toBounds, configAtEnd,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+ }
+
+ /**
+ * Animates resizing of the pinned stack given the duration.
+ *
+ * @param configAtEnd true if we are delaying config updates until the transition ends.
+ * @param duration the suggested duration to run the animation; the component responsible
+ * for running the animator will get this as an extra.
+ */
+ public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) {
if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
return;
}
@@ -170,7 +182,7 @@
if (configAtEnd) {
wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken);
}
- mPipTransitionController.startResizeTransition(wct);
+ mPipTransitionController.startResizeTransition(wct, duration);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index a9f3b54..0c4ed26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -713,15 +713,13 @@
}
}
- private void animateToMaximizedState(Runnable callback) {
- Rect maxMovementBounds = new Rect();
+ private void animateToMaximizedState() {
Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
mPipBoundsState.getMaxSize().y);
- mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds,
- mIsImeShowing ? mImeHeight : 0);
+
mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds,
- mPipBoundsState.getMovementBounds(), maxMovementBounds,
- callback);
+ getMovementBounds(mPipBoundsState.getBounds()),
+ getMovementBounds(maxBounds), null /* callback */);
}
private void animateToNormalSize(Runnable callback) {
@@ -729,22 +727,20 @@
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
- final Rect normalBounds = mPipBoundsState.getNormalBounds();
- final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds,
- minMenuSize);
- Rect restoredMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(destBounds,
- mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
- mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds,
- mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback);
+ final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio());
+ final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
+ final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(
+ normalBounds, minMenuSize);
+
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds,
+ getMovementBounds(mPipBoundsState.getBounds()),
+ getMovementBounds(adjustedNormalBounds), callback /* callback */);
}
private void animateToUnexpandedState(Rect restoreBounds) {
- Rect restoredMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
- mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
- restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
+ getMovementBounds(restoreBounds),
+ getMovementBounds(mPipBoundsState.getBounds()), false /* immediate */);
mSavedSnapFraction = -1f;
}
@@ -919,10 +915,6 @@
&& mMenuState != MENU_STATE_FULL) {
// If using pinch to zoom, double-tap functions as resizing between max/min size
if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
- final boolean toExpand = mPipBoundsState.getBounds().width()
- < mPipBoundsState.getMaxSize().x
- && mPipBoundsState.getBounds().height()
- < mPipBoundsState.getMaxSize().y;
if (mMenuController.isMenuVisible()) {
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
}
@@ -934,7 +926,7 @@
// actually toggle to the size chosen
if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
- animateToMaximizedState(null);
+ animateToMaximizedState();
} else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
animateToNormalSize(null);
@@ -1045,7 +1037,9 @@
private Rect getMovementBounds(Rect curBounds) {
Rect movementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
movementBounds, mIsImeShowing ? mImeHeight : 0);
return movementBounds;
}
@@ -1091,6 +1085,7 @@
case PipTransitionState.ENTERED_PIP:
onActivityPinned();
mTouchState.setAllowInputEvents(true);
+ mTouchState.reset();
break;
case PipTransitionState.EXITED_PIP:
mTouchState.setAllowInputEvents(false);
@@ -1101,6 +1096,7 @@
break;
case PipTransitionState.CHANGED_PIP_BOUNDS:
mTouchState.setAllowInputEvents(true);
+ mTouchState.reset();
break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 57dc5f9..683d30d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -71,6 +71,9 @@
static final String PIP_START_TX = "pip_start_tx";
static final String PIP_FINISH_TX = "pip_finish_tx";
static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds";
+ static final String ANIMATING_BOUNDS_CHANGE_DURATION =
+ "animating_bounds_change_duration";
+ static final int BOUNDS_CHANGE_JUMPCUT_DURATION = 0;
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -87,7 +90,7 @@
private final PipTransitionState mPipTransitionState;
//
- // Transition tokens
+ // Transition caches
//
@Nullable
@@ -96,6 +99,8 @@
private IBinder mExitViaExpandTransition;
@Nullable
private IBinder mResizeTransition;
+ private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+
//
// Internal state and relevant cached info
@@ -152,11 +157,12 @@
}
@Override
- public void startResizeTransition(WindowContainerTransaction wct) {
+ public void startResizeTransition(WindowContainerTransaction wct, int duration) {
if (wct == null) {
return;
}
mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+ mBoundsChangeDuration = duration;
}
@Nullable
@@ -272,6 +278,10 @@
extra.putParcelable(PIP_START_TX, startTransaction);
extra.putParcelable(PIP_FINISH_TX, finishTransaction);
extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds());
+ if (mBoundsChangeDuration > BOUNDS_CHANGE_JUMPCUT_DURATION) {
+ extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, mBoundsChangeDuration);
+ mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+ }
mFinishCallback = finishCallback;
mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 9d599ca..29272be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -29,6 +29,7 @@
import com.android.internal.util.Preconditions;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -62,6 +63,8 @@
* and throw an <code>IllegalStateException</code> otherwise.</p>
*/
public class PipTransitionState {
+ private static final String TAG = PipTransitionState.class.getSimpleName();
+
public static final int UNDEFINED = 0;
// State for Launcher animating the swipe PiP to home animation.
@@ -190,8 +193,9 @@
"No extra bundle for " + stateToString(state) + " state.");
}
if (mState != state) {
- dispatchPipTransitionStateChanged(mState, state, extra);
+ final int prevState = mState;
mState = state;
+ dispatchPipTransitionStateChanged(prevState, mState, extra);
}
}
@@ -319,4 +323,11 @@
return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
stateToString(mState), mInSwipePipToHomeTransition);
}
+
+ /** Dumps internal state. */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mState=" + stateToString(mState));
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 6325c68..ea8c0eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -390,7 +390,8 @@
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
int mainWindowShiftLength, float roundedCornerRadius) {
- mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
+ mFromYDelta = firstWindowFrame.top
+ - Math.max(firstWindowFrame.top, roundedCornerRadius);
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index c93e584..ee6c81a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -21,7 +21,7 @@
import com.android.internal.protolog.ProtoLog
/**
- * Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for
+ * Log messages using an API similar to [com.android.internal.protolog.ProtoLog]. Useful for
* logging from Kotlin classes as ProtoLog does not have support for Kotlin.
*
* All messages are logged to logcat if logging is enabled for that [IProtoLogGroup].
@@ -29,42 +29,42 @@
// TODO(b/168581922): remove once ProtoLog adds support for Kotlin
class KtProtoLog {
companion object {
- /** @see [com.android.internal.protolog.common.ProtoLog.d] */
+ /** @see [com.android.internal.protolog.ProtoLog.d] */
fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
if (group.isLogToLogcat) {
Log.d(group.tag, String.format(messageString, *args))
}
}
- /** @see [com.android.internal.protolog.common.ProtoLog.v] */
+ /** @see [com.android.internal.protolog.ProtoLog.v] */
fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
if (group.isLogToLogcat) {
Log.v(group.tag, String.format(messageString, *args))
}
}
- /** @see [com.android.internal.protolog.common.ProtoLog.i] */
+ /** @see [com.android.internal.protolog.ProtoLog.i] */
fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
if (group.isLogToLogcat) {
Log.i(group.tag, String.format(messageString, *args))
}
}
- /** @see [com.android.internal.protolog.common.ProtoLog.w] */
+ /** @see [com.android.internal.protolog.ProtoLog.w] */
fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
if (group.isLogToLogcat) {
Log.w(group.tag, String.format(messageString, *args))
}
}
- /** @see [com.android.internal.protolog.common.ProtoLog.e] */
+ /** @see [com.android.internal.protolog.ProtoLog.e] */
fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
if (group.isLogToLogcat) {
Log.e(group.tag, String.format(messageString, *args))
}
}
- /** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
+ /** @see [com.android.internal.protolog.ProtoLog.wtf] */
fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
if (group.isLogToLogcat) {
Log.wtf(group.tag, String.format(messageString, *args))
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f53c21d..2a95fa3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -31,6 +31,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -600,12 +601,13 @@
if (mAppIconBitmap != null && mAppName != null) {
return;
}
- final ActivityInfo activityInfo = mTaskInfo.topActivityInfo;
- if (activityInfo == null) {
- Log.e(TAG, "Top activity info not found in task");
+ final ComponentName baseActivity = mTaskInfo.baseActivity;
+ if (baseActivity == null) {
+ Log.e(TAG, "Base activity component not found in task");
return;
}
- PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ final PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
final BaseIconFactory headerIconFactory = createIconFactory(mContext,
@@ -619,6 +621,8 @@
final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
mAppName = pm.getApplicationLabel(applicationInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Base activity's component name cannot be found on the system");
} finally {
Trace.endSection();
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 35ed8de..7873a85 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -65,7 +65,7 @@
setup {
standardAppHelper.launchViaIntent(
wmHelper,
- YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
standardAppHelper.waitForVideoPlaying()
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 879034f..5c539a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -73,7 +73,7 @@
setup {
standardAppHelper.launchViaIntent(
wmHelper,
- YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
standardAppHelper.enterFullscreen()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index c671fbe..b5a6d83 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -48,6 +48,8 @@
fun setup() {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+ // TODO: b/349075982 - Remove once launcher rotation and checks are stable.
+ tapl.setExpectedRotationCheckEnabled(false)
SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 5f36e9a..a3b0dc5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1087,13 +1087,17 @@
fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
val fullscreenTask = createFullscreenTask()
- val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+ assertThat(wct?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
+ wct.assertReorderAt(1, homeTask, toTop = false)
}
@Test
@@ -1916,6 +1920,26 @@
}
@Test
+ fun toggleBounds_togglesToCalculatedBoundsForNonResizable() {
+ val bounds = Rect(0, 0, 200, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo = ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = false
+ }
+
+ // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
+ val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
+
+ controller.toggleDesktopTaskSize(task)
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ }
+
+ @Test
fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
val bounds = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
@@ -1942,6 +1966,46 @@
}
@Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
+ isResizeable = false
+ }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
+ boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
+ isResizeable = false
+ }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
+ STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ }
+
+ @Test
fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
val boundsBeforeMaximize = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 36e8a46..8165e59 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -170,7 +170,7 @@
}
@Before
- public void setUp() {
+ public void setUp() throws PackageManager.NameNotFoundException {
mMockitoSession = mockitoSession()
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus.class)
@@ -186,6 +186,9 @@
mTestableContext.ensureTestableResources();
mContext.setMockPackageManager(mMockPackageManager);
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel");
+ final ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo);
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
@@ -608,8 +611,6 @@
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(visible)
.build();
- taskInfo.topActivityInfo = new ActivityInfo();
- taskInfo.topActivityInfo.applicationInfo = new ApplicationInfo();
taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e134c23..124f1f0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7007,6 +7007,23 @@
}
/**
+ * Check whether a user can mute this stream type from a given UI element.
+ *
+ * <p>Only useful for volume controllers.
+ *
+ * @param streamType type of stream to check if it's mutable from UI
+ *
+ * @hide
+ */
+ public boolean isStreamMutableByUi(int streamType) {
+ try {
+ return getService().isStreamMutableByUi(streamType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Only useful for volume controllers.
* @hide
*/
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index c8b9da5..08cc126 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -305,6 +305,8 @@
boolean isStreamAffectedByMute(int streamType);
+ boolean isStreamMutableByUi(int streamType);
+
void disableSafeMediaVolume(String callingPackage);
oneway void lowerVolumeToRs1(String callingPackage);
diff --git a/packages/InputDevices/AndroidManifest.xml b/packages/InputDevices/AndroidManifest.xml
index da8c0d3..01b0bc7 100644
--- a/packages/InputDevices/AndroidManifest.xml
+++ b/packages/InputDevices/AndroidManifest.xml
@@ -19,5 +19,23 @@
<meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS"
android:resource="@xml/keyboard_layouts" />
</receiver>
+
+ <receiver android:name=".KeyGlyphMapProvider"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.hardware.input.action.QUERY_KEYBOARD_GLYPH_MAPS" />
+ </intent-filter>
+ <meta-data android:name="android.hardware.input.metadata.KEYBOARD_GLYPH_MAPS"
+ android:resource="@xml/keyboard_glyph_maps" />
+ </receiver>
+
+ <receiver android:name=".OverlayKeyGlyphMapProvider"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.hardware.input.action.QUERY_KEYBOARD_GLYPH_MAPS" />
+ </intent-filter>
+ <meta-data android:name="android.hardware.input.metadata.KEYBOARD_GLYPH_MAPS"
+ android:resource="@xml/keyboard_glyph_maps_overlay" />
+ </receiver>
</application>
</manifest>
diff --git a/packages/InputDevices/res/xml/keyboard_glyph_maps.xml b/packages/InputDevices/res/xml/keyboard_glyph_maps.xml
new file mode 100644
index 0000000..b7a6f3f
--- /dev/null
+++ b/packages/InputDevices/res/xml/keyboard_glyph_maps.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<keyboard-glyph-maps xmlns:android="http://schemas.android.com/apk/res/android">
+<!-- <key-glyph-map-->
+<!-- android:glyphMap="@xml/xyz_glyph_map"-->
+<!-- android:vendorId="0x1234"-->
+<!-- android:productId="0x3456" />-->
+</keyboard-glyph-maps>
\ No newline at end of file
diff --git a/packages/InputDevices/res/xml/keyboard_glyph_maps_overlay.xml b/packages/InputDevices/res/xml/keyboard_glyph_maps_overlay.xml
new file mode 100644
index 0000000..7c036eb
--- /dev/null
+++ b/packages/InputDevices/res/xml/keyboard_glyph_maps_overlay.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<keyboard-glyph-maps xmlns:android="http://schemas.android.com/apk/res/android">
+<!-- NOTE: This is reserved for glyph maps to be provided using RRO. For providing glyph maps in
+ AOSP use key_glyph_maps.xml instead -->
+</keyboard-glyph-maps>
\ No newline at end of file
diff --git a/packages/InputDevices/src/com/android/inputdevices/KeyGlyphMapProvider.java b/packages/InputDevices/src/com/android/inputdevices/KeyGlyphMapProvider.java
new file mode 100644
index 0000000..6b508dd
--- /dev/null
+++ b/packages/InputDevices/src/com/android/inputdevices/KeyGlyphMapProvider.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.android.inputdevices;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class KeyGlyphMapProvider extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Nothing to do at this time.
+ }
+}
diff --git a/packages/InputDevices/src/com/android/inputdevices/OverlayKeyGlyphMapProvider.java b/packages/InputDevices/src/com/android/inputdevices/OverlayKeyGlyphMapProvider.java
new file mode 100644
index 0000000..13b5b42
--- /dev/null
+++ b/packages/InputDevices/src/com/android/inputdevices/OverlayKeyGlyphMapProvider.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.android.inputdevices;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class OverlayKeyGlyphMapProvider extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Nothing to do at this time.
+ }
+}
diff --git a/packages/PackageInstaller/res/layout/install_content_view.xml b/packages/PackageInstaller/res/layout/install_content_view.xml
index 524a88a..affcca1 100644
--- a/packages/PackageInstaller/res/layout/install_content_view.xml
+++ b/packages/PackageInstaller/res/layout/install_content_view.xml
@@ -34,7 +34,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/message_staging" />
<ProgressBar
@@ -57,7 +57,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/installing" />
<ProgressBar
@@ -74,7 +74,7 @@
android:id="@+id/install_confirm_question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/install_confirm_question"
android:visibility="invisible"
android:scrollbars="vertical" />
@@ -83,7 +83,7 @@
android:id="@+id/install_confirm_question_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/install_confirm_question_update"
android:visibility="invisible"
android:scrollbars="vertical" />
@@ -92,7 +92,7 @@
android:id="@+id/install_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/install_done"
android:visibility="invisible" />
@@ -100,7 +100,7 @@
android:id="@+id/install_failed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/install_failed"
android:visibility="invisible" />
@@ -108,7 +108,7 @@
android:id="@+id/install_failed_blocked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/install_failed_blocked"
android:visibility="invisible" />
@@ -116,7 +116,7 @@
android:id="@+id/install_failed_conflict"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/install_failed_conflict"
android:visibility="invisible" />
@@ -124,7 +124,7 @@
android:id="@+id/install_failed_incompatible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/install_failed_incompatible"
android:visibility="invisible" />
@@ -132,7 +132,7 @@
android:id="@+id/install_failed_invalid_apk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle"
android:text="@string/install_failed_invalid_apk"
android:visibility="invisible" />
diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
index 434e333..d5c5d8b 100644
--- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml
+++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
@@ -22,32 +22,32 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:theme="?android:attr/alertDialogTheme"
- android:orientation="vertical"
- android:paddingTop="8dp"
- android:paddingStart="?android:attr/dialogPreferredPadding"
- android:paddingEnd="?android:attr/dialogPreferredPadding"
- android:clipToPadding="false">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="?android:attr/alertDialogTheme"
+ android:orientation="vertical"
+ android:paddingTop="8dp"
+ android:paddingStart="?android:attr/dialogPreferredPadding"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:clipToPadding="false">
- <TextView
- android:id="@+id/message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead" />
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" />
- <CheckBox
- android:id="@+id/keepData"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginStart="-8dp"
- android:paddingLeft="8sp"
- android:visibility="gone"
- style="@android:style/TextAppearance.Material.Subhead" />
+ <CheckBox
+ android:id="@+id/keepData"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="-8dp"
+ android:paddingStart="8sp"
+ android:paddingEnd="0sp"
+ android:visibility="gone"
+ style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" />
- </LinearLayout>
+ </LinearLayout>
</ScrollView>
diff --git a/packages/PackageInstaller/res/values-night/themes.xml b/packages/PackageInstaller/res/values-night/themes.xml
index a5b82b3..37588ad 100644
--- a/packages/PackageInstaller/res/values-night/themes.xml
+++ b/packages/PackageInstaller/res/values-night/themes.xml
@@ -19,7 +19,6 @@
<style name="Theme.AlertDialogActivity"
parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
- <item name="alertDialogStyle">@style/AlertDialog</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowAnimationStyle">@null</item>
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index f5af510..aa48712 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -19,7 +19,6 @@
<style name="Theme.AlertDialogActivity"
parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
- <item name="alertDialogStyle">@style/AlertDialog</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowAnimationStyle">@null</item>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index e0398aa..824dd4a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -330,7 +330,8 @@
// data we still want to count it as "installed".
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
- if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
+ // If the package is archived, treat it as update case.
+ if (!mAppInfo.isArchived && (mAppInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 2e9b7b4..c260426 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -95,7 +95,21 @@
*/
var stagedSessionId = SessionInfo.INVALID_ID
private set
+
+ /**
+ * UID of the last caller of Pia. This can point to a 3P installer if it uses intents to install
+ * an APK, or receives a
+ * [STATUS_PENDING_USER_ACTION][PackageInstaller.STATUS_PENDING_USER_ACTION] status code.
+ * It may point to Pia, when it receives the STATUS_PENDING_USER_ACTION status code in case of
+ * an update-ownership change.
+ */
private var callingUid = Process.INVALID_UID
+
+ /**
+ * UID of the origin of the installation. This UID is used to fetch the app-label of the
+ * source of the install, and also check whether the source app has the AppOp to install other
+ * apps.
+ */
private var originatingUid = Process.INVALID_UID
private var callingPackage: String? = null
private var sessionStager: SessionStager? = null
@@ -135,60 +149,60 @@
stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID)
callingPackage = callerInfo.packageName
-
- if (sessionId != SessionInfo.INVALID_ID) {
- val sessionInfo: SessionInfo? = packageInstaller.getSessionInfo(sessionId)
- callingPackage = sessionInfo?.getInstallerPackageName()
- callingAttributionTag = sessionInfo?.getInstallerAttributionTag()
- }
-
- // Uid of the source package, coming from ActivityManager
callingUid = callerInfo.uid
- if (callingUid == Process.INVALID_UID) {
- Log.e(LOG_TAG, "Could not determine the launching uid.")
- }
+
val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage)
- // Uid of the source package, with a preference to uid from ApplicationInfo
- originatingUid = sourceInfo?.uid ?: callingUid
- appOpRequestInfo = AppOpRequestInfo(
- getPackageNameForUid(context, originatingUid, callingPackage),
- originatingUid, callingAttributionTag
- )
-
- if(localLogv) {
- Log.i(LOG_TAG, "Intent: $intent\n" +
- "sessionId: $sessionId\n" +
- "staged sessionId: $stagedSessionId\n" +
- "calling package: $callingPackage\n" +
- "callingUid: $callingUid\n" +
- "originatingUid: $originatingUid")
- }
-
if (callingUid == Process.INVALID_UID && sourceInfo == null) {
// Caller's identity could not be determined. Abort the install
Log.e(LOG_TAG, "Cannot determine caller since UID is invalid and sourceInfo is null")
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
+ originatingUid = callingUid
+ val sessionInfo: SessionInfo? =
+ if (sessionId != SessionInfo.INVALID_ID)
+ packageInstaller.getSessionInfo(sessionId)
+ else null
+ if (sessionInfo != null) {
+ callingAttributionTag = sessionInfo.installerAttributionTag
+ if (sessionInfo.originatingUid != Process.INVALID_UID) {
+ originatingUid = sessionInfo.originatingUid
+ }
+ }
+
+ appOpRequestInfo = AppOpRequestInfo(
+ getPackageNameForUid(context, originatingUid, callingPackage),
+ originatingUid,
+ callingAttributionTag
+ )
+
+ if (localLogv) {
+ Log.i(
+ LOG_TAG, "Intent: $intent\n" +
+ "sessionId: $sessionId\n" +
+ "staged sessionId: $stagedSessionId\n" +
+ "calling package: $callingPackage\n" +
+ "callingUid: $callingUid\n" +
+ "originatingUid: $originatingUid\n" +
+ "sourceInfo: $sourceInfo"
+ )
+ }
+
if ((sessionId != SessionInfo.INVALID_ID
- && !isCallerSessionOwner(packageInstaller, originatingUid, sessionId))
+ && !isCallerSessionOwner(packageInstaller, callingUid, sessionId))
|| (stagedSessionId != SessionInfo.INVALID_ID
&& !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId))
) {
- Log.e(LOG_TAG, "UID is not the owner of the session:\n" +
- "CallingUid: $originatingUid | SessionId: $sessionId\n" +
- "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId")
+ Log.e(
+ LOG_TAG, "UID is not the owner of the session:\n" +
+ "CallingUid: $callingUid | SessionId: $sessionId\n" +
+ "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId"
+ )
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
- isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, originatingUid)
- if (!isInstallPermissionGrantedOrRequested(
- context, callingUid, originatingUid, isTrustedSource
- )
- ) {
- Log.e(LOG_TAG, "UID $originatingUid needs to declare " +
- Manifest.permission.REQUEST_INSTALL_PACKAGES
- )
+ isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, callingUid)
+ if (!isInstallPermissionGrantedOrRequested(context, callingUid, isTrustedSource)) {
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
@@ -196,7 +210,7 @@
if (restriction != null) {
val adminSupportDetailsIntent =
devicePolicyManager!!.createAdminSupportIntent(restriction)
- Log.e(LOG_TAG, "$restriction set in place. Cannot install." )
+ Log.e(LOG_TAG, "$restriction set in place. Cannot install.")
return InstallAborted(
ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent
)
@@ -221,12 +235,12 @@
private fun isInstallRequestFromTrustedSource(
sourceInfo: ApplicationInfo?,
intent: Intent,
- originatingUid: Int,
+ callingUid: Int,
): Boolean {
val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
return (sourceInfo != null && sourceInfo.isPrivilegedApp
&& (isNotUnknownSource
- || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, originatingUid)))
+ || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid)))
}
private fun getDevicePolicyRestrictions(): String? {
@@ -311,7 +325,7 @@
Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
),
activityResultCode = Activity.RESULT_FIRST_USER,
- errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
+ errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
)
return
}
@@ -392,8 +406,9 @@
Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e)
params.setSize(pfd.statSize)
} catch (e: IOException) {
- Log.e(LOG_TAG, "Cannot calculate installed size $debugPathName. " +
- "Try only apk size.", e
+ Log.e(
+ LOG_TAG, "Cannot calculate installed size $debugPathName. " +
+ "Try only apk size.", e
)
}
} else {
@@ -651,7 +666,11 @@
private fun getUpdateMessage(pkgInfo: PackageInfo, userActionReason: Int): String? {
if (isAppUpdating(pkgInfo)) {
val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo)
- val requestedUpdateOwnerLabel = getApplicationLabel(callingPackage)
+
+ val originatingPackageName =
+ getPackageNameForUid(context, originatingUid, callingPackage)
+ val requestedUpdateOwnerLabel = getApplicationLabel(originatingPackageName)
+
if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
&& userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP
) {
@@ -754,14 +773,14 @@
}
private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage {
- if (requestInfo.callingPackage == null) {
+ if (requestInfo.originatingPackage == null) {
Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName)
return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE)
}
// Shouldn't use static constant directly, see b/65534401.
val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES)
val appOpMode = appOpsManager!!.noteOpNoThrow(
- appOpStr!!, requestInfo.originatingUid, requestInfo.callingPackage,
+ appOpStr!!, requestInfo.originatingUid, requestInfo.originatingPackage,
requestInfo.attributionTag, "Started package installation activity"
)
if (localLogv) {
@@ -772,20 +791,20 @@
AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> {
if (appOpMode == AppOpsManager.MODE_DEFAULT) {
appOpsManager.setMode(
- appOpStr, requestInfo.originatingUid, requestInfo.callingPackage,
+ appOpStr, requestInfo.originatingUid, requestInfo.originatingPackage,
AppOpsManager.MODE_ERRORED
)
}
try {
val sourceInfo =
- packageManager.getApplicationInfo(requestInfo.callingPackage, 0)
+ packageManager.getApplicationInfo(requestInfo.originatingPackage, 0)
val sourceAppSnippet = getAppSnippet(context, sourceInfo)
InstallUserActionRequired(
USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet,
- dialogMessage = requestInfo.callingPackage
+ sourceApp = requestInfo.originatingPackage
)
} catch (e: PackageManager.NameNotFoundException) {
- Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.callingPackage)
+ Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.originatingPackage)
InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
}
@@ -827,8 +846,10 @@
setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null)
} catch (e: PackageManager.NameNotFoundException) {
setStageBasedOnResult(
- PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
- null)
+ PackageInstaller.STATUS_FAILURE,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+ null
+ )
}
return
}
@@ -848,7 +869,8 @@
}
} catch (e: OutOfIdsException) {
setStageBasedOnResult(
- PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null
+ )
return
}
val broadcastIntent = Intent(BROADCAST_ACTION)
@@ -866,7 +888,8 @@
Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e)
packageInstaller.abandonSession(stagedSessionId)
setStageBasedOnResult(
- PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null
+ )
}
}
@@ -876,9 +899,11 @@
message: String?,
) {
if (localLogv) {
- Log.i(LOG_TAG, "Status code: $statusCode\n" +
- "legacy status: $legacyStatus\n" +
- "message: $message")
+ Log.i(
+ LOG_TAG, "Status code: $statusCode\n" +
+ "legacy status: $legacyStatus\n" +
+ "message: $message"
+ )
}
if (statusCode == PackageInstaller.STATUS_SUCCESS) {
val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
@@ -891,11 +916,12 @@
_installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
} else {
if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
- _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+ _installResult.setValue(
+ InstallFailed(appSnippet, statusCode, legacyStatus, message)
+ )
} else {
_installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR))
}
-
}
}
@@ -939,7 +965,7 @@
data class CallerInfo(val packageName: String?, val uid: Int)
data class AppOpRequestInfo(
- val callingPackage: String?,
+ val originatingPackage: String?,
val originatingUid: Int,
val attributionTag: String?,
)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
index bbb9bca..5dd4d29 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -43,7 +43,10 @@
val actionReason: Int,
private val appSnippet: PackageUtil.AppSnippet? = null,
val isAppUpdating: Boolean = false,
- val dialogMessage: String? = null,
+ /**
+ * This holds either a package name or the app label of the install source.
+ */
+ val sourceApp: String? = null,
) : InstallStage(STAGE_USER_ACTION_REQUIRED) {
val appIcon: Drawable?
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
index bae6f68..8572852 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -128,18 +128,19 @@
/**
* @param context the [Context] object
- * @param callingUid the UID of the caller who's permission is being checked
- * @param originatingUid the UID from where install is being originated. This could be same as
- * callingUid or it will be the UID of the package performing a session based install
- * @param isTrustedSource whether install request is coming from a privileged app or an app that
- * has [Manifest.permission.INSTALL_PACKAGES] permission granted
- * @return `true` if the package is granted the said permission
+ * @param callingUid the UID of the caller of Pia
+ * @param isTrustedSource indicates whether install request is coming from a privileged app
+ * that has passed EXTRA_NOT_UNKNOWN_SOURCE as `true` in the installation intent, or that has
+ * the [INSTALL_PACKAGES][Manifest.permission.INSTALL_PACKAGES] permission granted.
+ *
+ * @return `true` if the package is either a system downloads provider, a document manager,
+ * a trusted source, or has declared the
+ * [REQUEST_INSTALL_PACKAGES][Manifest.permission.REQUEST_INSTALL_PACKAGES] in its manifest.
*/
@JvmStatic
fun isInstallPermissionGrantedOrRequested(
context: Context,
callingUid: Int,
- originatingUid: Int,
isTrustedSource: Boolean,
): Boolean {
val isDocumentsManager =
@@ -148,19 +149,18 @@
getSystemDownloadsProviderInfo(context.packageManager, callingUid) != null
if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
- val targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid)
+ val targetSdkVersion = getMaxTargetSdkVersionForUid(context, callingUid)
if (targetSdkVersion < 0) {
- // Invalid originating uid supplied. Abort install.
- Log.w(LOG_TAG, "Cannot get target sdk version for uid $originatingUid")
+ // Invalid calling uid supplied. Abort install.
+ Log.e(LOG_TAG, "Cannot get target SDK version for uid $callingUid")
return false
} else if (targetSdkVersion >= Build.VERSION_CODES.O
&& !isUidRequestingPermission(
- context.packageManager, originatingUid,
- Manifest.permission.REQUEST_INSTALL_PACKAGES
+ context.packageManager, callingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES
)
) {
Log.e(
- LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ LOG_TAG, "Requesting uid " + callingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES
)
return false
@@ -204,13 +204,13 @@
* @return `true` if the caller is the session owner
*/
@JvmStatic
- fun isCallerSessionOwner(pi: PackageInstaller, originatingUid: Int, sessionId: Int): Boolean {
- if (originatingUid == Process.ROOT_UID) {
+ fun isCallerSessionOwner(pi: PackageInstaller, callingUid: Int, sessionId: Int): Boolean {
+ if (callingUid == Process.ROOT_UID) {
return true
}
val sessionInfo = pi.getSessionInfo(sessionId) ?: return false
val installerUid = sessionInfo.getInstallerUid()
- return originatingUid == installerUid
+ return callingUid == installerUid
}
/**
@@ -362,8 +362,8 @@
* @return the packageName corresponding to a UID.
*/
@JvmStatic
- fun getPackageNameForUid(context: Context, sourceUid: Int, callingPackage: String?): String? {
- if (sourceUid == Process.INVALID_UID) {
+ fun getPackageNameForUid(context: Context, uid: Int, preferredPkgName: String?): String? {
+ if (uid == Process.INVALID_UID) {
return null
}
// If the sourceUid belongs to the system downloads provider, we explicitly return the
@@ -371,20 +371,21 @@
// packages, resulting in uncertainty about which package will end up first in the list
// of packages associated with this UID
val pm = context.packageManager
- val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, sourceUid)
+ val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, uid)
if (systemDownloadProviderInfo != null) {
return systemDownloadProviderInfo.packageName
}
- val packagesForUid = pm.getPackagesForUid(sourceUid) ?: return null
+
+ val packagesForUid = pm.getPackagesForUid(uid) ?: return null
if (packagesForUid.size > 1) {
- if (callingPackage != null) {
+ Log.i(LOG_TAG, "Multiple packages found for source uid $uid")
+ if (preferredPkgName != null) {
for (packageName in packagesForUid) {
- if (packageName == callingPackage) {
+ if (packageName == preferredPkgName) {
return packageName
}
}
}
- Log.i(LOG_TAG, "Multiple packages found for source uid $sourceUid")
}
return packagesForUid[0]
}
@@ -439,7 +440,7 @@
*/
data class AppSnippet(var label: CharSequence?, var icon: Drawable?) {
override fun toString(): String {
- return "AppSnippet[label = ${label}, hasIcon = ${icon != null}]"
+ return "AppSnippet[label = $label, hasIcon = ${icon != null}]"
}
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
index a95137d..343a213 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -63,7 +63,7 @@
.setMessage(R.string.untrusted_external_source_warning)
.setPositiveButton(R.string.external_sources_settings,
(dialog, which) -> mInstallActionListener.sendUnknownAppsIntent(
- mDialogData.getDialogMessage()))
+ mDialogData.getSourceApp()))
.setNegativeButton(R.string.cancel,
(dialog, which) -> mInstallActionListener.onNegativeResponse(
mDialogData.getStageCode()))
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 99b1eec..e186590 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -64,7 +64,7 @@
int positiveBtnTextRes;
if (mDialogData.isAppUpdating()) {
- if (mDialogData.getDialogMessage() != null) {
+ if (mDialogData.getSourceApp() != null) {
positiveBtnTextRes = R.string.update_anyway;
} else {
positiveBtnTextRes = R.string.update;
@@ -88,9 +88,10 @@
TextView viewToEnable;
if (mDialogData.isAppUpdating()) {
viewToEnable = dialogView.requireViewById(R.id.install_confirm_question_update);
- String dialogMessage = mDialogData.getDialogMessage();
- if (dialogMessage != null) {
- viewToEnable.setText(Html.fromHtml(dialogMessage, Html.FROM_HTML_MODE_LEGACY));
+ String sourcePackageName = mDialogData.getSourceApp();
+ if (sourcePackageName != null) {
+ // Show the update-ownership change message
+ viewToEnable.setText(Html.fromHtml(sourcePackageName, Html.FROM_HTML_MODE_LEGACY));
}
} else {
viewToEnable = dialogView.requireViewById(R.id.install_confirm_question);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
index 3f19830..960df63 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
@@ -20,6 +20,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.app.AutomaticZenRule;
import android.content.Context;
@@ -120,7 +121,14 @@
}
private static Drawable getFallbackIcon(Context context, int ruleType) {
- int iconResIdFromType = switch (ruleType) {
+ int iconResIdFromType = getIconResourceIdFromType(ruleType);
+ return requireNonNull(context.getDrawable(iconResIdFromType));
+ }
+
+ /** Return the default icon resource associated to a {@link AutomaticZenRule.Type} */
+ @DrawableRes
+ public static int getIconResourceIdFromType(@AutomaticZenRule.Type int ruleType) {
+ return switch (ruleType) {
case AutomaticZenRule.TYPE_UNKNOWN ->
com.android.internal.R.drawable.ic_zen_mode_type_unknown;
case AutomaticZenRule.TYPE_OTHER ->
@@ -141,7 +149,6 @@
com.android.internal.R.drawable.ic_zen_mode_type_managed;
default -> com.android.internal.R.drawable.ic_zen_mode_type_unknown;
};
- return requireNonNull(context.getDrawable(iconResIdFromType));
}
private static Drawable getMonochromeIconIfPresent(Drawable icon) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 33d39f0..2cc9112 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -23,6 +23,7 @@
import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
@@ -33,12 +34,15 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.service.notification.SystemZenRules;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.util.Log;
+import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -56,11 +60,12 @@
* <p>It also adapts other rule features that we don't want to expose in the UI, such as
* interruption filters other than {@code PRIORITY}, rules without specific icons, etc.
*/
-public class ZenMode {
+public class ZenMode implements Parcelable {
private static final String TAG = "ZenMode";
static final String MANUAL_DND_MODE_ID = "manual_dnd";
+ static final String TEMP_NEW_MODE_ID = "temp_new_mode";
// Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS =
@@ -125,6 +130,25 @@
isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true);
}
+ /**
+ * Returns a new {@link ZenMode} instance that can represent a custom_manual mode that is in the
+ * process of being created (and not yet saved).
+ *
+ * @param name mode name
+ * @param iconResId resource id of the chosen icon, {code 0} if none.
+ */
+ public static ZenMode newCustomManual(String name, @DrawableRes int iconResId) {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder(name,
+ ZenModeConfig.toCustomManualConditionId())
+ .setPackage(ZenModeConfig.getCustomManualConditionProvider().getPackageName())
+ .setType(AutomaticZenRule.TYPE_OTHER)
+ .setOwner(ZenModeConfig.getCustomManualConditionProvider())
+ .setIconResId(iconResId)
+ .setManualInvocationAllowed(true)
+ .build();
+ return new ZenMode(TEMP_NEW_MODE_ID, rule, Status.ENABLED, false);
+ }
+
private ZenMode(String id, @NonNull AutomaticZenRule rule, Status status, boolean isManualDnd) {
mId = id;
mRule = rule;
@@ -304,4 +328,34 @@
public String toString() {
return mId + " (" + mStatus + ") -> " + mRule;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeParcelable(mRule, 0);
+ dest.writeString(mStatus.name());
+ dest.writeBoolean(mIsManualDnd);
+ }
+
+ public static final Creator<ZenMode> CREATOR = new Creator<ZenMode>() {
+ @Override
+ public ZenMode createFromParcel(Parcel in) {
+ return new ZenMode(
+ in.readString(),
+ checkNotNull(in.readParcelable(AutomaticZenRule.class.getClassLoader(),
+ AutomaticZenRule.class)),
+ Status.valueOf(in.readString()),
+ in.readBoolean());
+ }
+
+ @Override
+ public ZenMode[] newArray(int size) {
+ return new ZenMode[size];
+ }
+ };
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
index 5529da0..d36e2fe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
@@ -27,6 +27,8 @@
import android.service.notification.ZenModeConfig;
import android.util.Log;
+import androidx.annotation.DrawableRes;
+
import com.android.settingslib.R;
import java.time.Duration;
@@ -184,18 +186,13 @@
* Creates a new custom mode with the provided {@code name}. The mode will be "manual" (i.e.
* not have a schedule), this can be later updated by the user in the mode settings page.
*
+ * @param name mode name
+ * @param iconResId resource id of the chosen icon, {code 0} if none.
* @return the created mode. Only {@code null} if creation failed due to an internal error
*/
@Nullable
- public ZenMode addCustomMode(String name) {
- AutomaticZenRule rule = new AutomaticZenRule.Builder(name,
- ZenModeConfig.toCustomManualConditionId())
- .setPackage(ZenModeConfig.getCustomManualConditionProvider().getPackageName())
- .setType(AutomaticZenRule.TYPE_OTHER)
- .setOwner(ZenModeConfig.getCustomManualConditionProvider())
- .setManualInvocationAllowed(true)
- .build();
-
+ public ZenMode addCustomManualMode(String name, @DrawableRes int iconResId) {
+ AutomaticZenRule rule = ZenMode.newCustomManual(name, iconResId).getRule();
String ruleId = mNotificationManager.addAutomaticZenRule(rule);
return getMode(ruleId);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 837c682..c88c4c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -182,7 +182,7 @@
minVolume = getMinVolume(audioStream),
maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
volume = audioManager.getStreamVolume(audioStream.value),
- isAffectedByMute = audioManager.isStreamAffectedByMute(audioStream.value),
+ isAffectedByMute = audioManager.isStreamMutableByUi(audioStream.value),
isAffectedByRingerMode = audioManager.isStreamAffectedByRingerMode(audioStream.value),
isMuted = audioManager.isStreamMute(audioStream.value),
)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index 32cdb98..9e54312 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -21,13 +21,17 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.app.AutomaticZenRule;
import android.net.Uri;
+import android.os.Parcel;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
+import com.android.internal.R;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -161,6 +165,30 @@
assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY);
}
+ @Test
+ public void writeToParcel_equals() {
+ assertUnparceledIsEqualToOriginal("example",
+ new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false)));
+
+ assertUnparceledIsEqualToOriginal("dnd", ZenMode.manualDndMode(ZEN_RULE, true));
+
+ assertUnparceledIsEqualToOriginal("custom_manual",
+ ZenMode.newCustomManual("New mode", R.drawable.ic_zen_mode_type_immersive));
+ }
+
+ private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ZenMode unparceled = ZenMode.CREATOR.createFromParcel(parcel);
+
+ assertWithMessage("Comparing " + type).that(unparceled).isEqualTo(original);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
private static ZenModeConfig.ZenRule zenConfigRuleFor(AutomaticZenRule azr, boolean isActive) {
ZenModeConfig.ZenRule zenRule = new ZenModeConfig.ZenRule();
zenRule.pkg = azr.getPackageName();
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 097840e..5ddf005 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -45,7 +45,7 @@
<integer name="def_location_mode">3</integer>
<!-- 0 == off, 1 == on-->
<integer name="def_paired_device_location_mode">1</integer>
- <bool name="assisted_gps_enabled">true</bool>
+ <bool name="assisted_gps_enabled">false</bool>
<bool name="def_netstats_enabled">true</bool>
<bool name="def_usb_mass_storage_enabled">true</bool>
<bool name="def_wifi_on">false</bool>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index bd7c9a0..b5776e2 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -193,7 +193,7 @@
"tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
"tests/src/**/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt",
"tests/src/**/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt",
- "tests/src/**/systemui/navigationbar/NavigationBarButtonTest.java",
+ "tests/src/**/systemui/navigationbar/views/NavigationBarButtonTest.java",
"tests/src/**/systemui/people/PeopleProviderTest.java",
"tests/src/**/systemui/people/PeopleSpaceUtilsTest.java",
"tests/src/**/systemui/people/widget/PeopleSpaceWidgetManagerTest.java",
@@ -449,7 +449,7 @@
"tests/src/**/systemui/doze/DozeScreenStateTest.java",
"tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java",
"tests/src/**/systemui/media/dialog/MediaOutputControllerTest.java",
- "tests/src/**/systemui/navigationbar/NavigationBarTest.java",
+ "tests/src/**/systemui/navigationbar/views/NavigationBarTest.java",
"tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
"tests/src/**/systemui/power/PowerUITest.java",
"tests/src/**/systemui/qs/QSFooterViewControllerTest.java",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b37db16..666d939 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -280,6 +280,9 @@
<!-- to adjust volume in volume panel -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <!-- to get bluetooth audio device category -->
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
+
<!-- to access ResolverRankerServices -->
<uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f5153e1..83b7455 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -146,6 +146,16 @@
}
flag {
+ name: "notification_transparent_header_fix"
+ namespace: "systemui"
+ description: "fix the transparent group header issue for async header inflation."
+ bug: "340161724"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "pss_app_selector_abrupt_exit_fix"
namespace: "systemui"
description: "Fixes the app selector abruptly disappearing without an animation, when the"
@@ -1045,14 +1055,6 @@
bug: "343505271"
}
-
-flag {
- name: "new_touchpad_gestures_tutorial"
- namespace: "systemui"
- description: "Enables new interactive tutorial for learning touchpad gestures"
- bug: "309928033"
-}
-
flag {
name: "register_wallpaper_notifier_background"
namespace: "systemui"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index e02e5f8..bb76c1d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -160,7 +160,7 @@
viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
val backgroundType by
viewModel.communalBackground.collectAsStateWithLifecycle(
- initialValue = CommunalBackgroundType.DEFAULT
+ initialValue = CommunalBackgroundType.ANIMATED
)
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
@@ -243,7 +243,7 @@
) {
Box(modifier = Modifier.element(Communal.Elements.Scrim).fillMaxSize()) {
when (backgroundType) {
- CommunalBackgroundType.DEFAULT -> DefaultBackground(colors = colors)
+ CommunalBackgroundType.STATIC -> DefaultBackground(colors = colors)
CommunalBackgroundType.STATIC_GRADIENT -> StaticLinearGradient()
CommunalBackgroundType.ANIMATED -> AnimatedLinearGradient()
CommunalBackgroundType.NONE -> BackgroundTopScrim()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 4eae14b3..07898b0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -108,7 +108,12 @@
private val draggingItemLayoutInfo: LazyGridItemInfo?
get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }
- internal fun onDragStart(offset: Offset, contentOffset: Offset) {
+ /**
+ * Called when dragging is initiated.
+ *
+ * @return {@code True} if dragging a grid item, {@code False} otherwise.
+ */
+ internal fun onDragStart(offset: Offset, contentOffset: Offset): Boolean {
state.layoutInfo.visibleItemsInfo
.filter { item -> contentListState.isItemEditable(item.index) }
// grid item offset is based off grid content container so we need to deduct
@@ -118,7 +123,10 @@
dragStartPointerOffset = offset - this.offset.toOffset()
draggingItemIndex = index
draggingItemInitialOffset = this.offset.toOffset()
+ return true
}
+
+ return false
}
internal fun onDragInterrupted() {
@@ -216,8 +224,9 @@
dragDropState.onDrag(offset = offset)
},
onDragStart = { offset ->
- dragDropState.onDragStart(offset, contentOffset)
- viewModel.onReorderWidgetStart()
+ if (dragDropState.onDragStart(offset, contentOffset)) {
+ viewModel.onReorderWidgetStart()
+ }
},
onDragEnd = {
dragDropState.onDragInterrupted()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
index 418df5c..0b96694 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
@@ -89,7 +89,7 @@
// Content.
val contentColor = LocalAndroidColorScheme.current.onSurfaceVariant
- Box(Modifier.defaultMinSize(minHeight = 48.dp)) {
+ Box {
CompositionLocalProvider(LocalContentColor provides contentColor) {
ProvideTextStyle(
MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.Center)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 2eea2f0..a568824 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -19,13 +19,19 @@
import android.util.Log
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -41,9 +47,11 @@
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -52,6 +60,7 @@
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -59,10 +68,12 @@
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -71,6 +82,7 @@
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.res.R
@@ -137,6 +149,87 @@
)
}
+/**
+ * A version of [HeadsUpNotificationSpace] that can be swiped up off the top edge of the screen by
+ * the user. When swiped up, the heads up notification is snoozed.
+ */
+@Composable
+fun SceneScope.SnoozeableHeadsUpNotificationSpace(
+ stackScrollView: NotificationScrollView,
+ viewModel: NotificationsPlaceholderViewModel,
+) {
+ val context = LocalContext.current
+ val density = LocalDensity.current
+ val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
+ val headsUpPadding =
+ with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
+
+ val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
+
+ var scrollOffset by remember { mutableFloatStateOf(0f) }
+ val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat())
+ val maxScrollOffset = 0f
+
+ val scrollableState = rememberScrollableState { delta ->
+ consumeDeltaWithinRange(
+ current = scrollOffset,
+ setCurrent = { scrollOffset = it },
+ min = minScrollOffset,
+ max = maxScrollOffset,
+ delta
+ )
+ }
+
+ val nestedScrollConnection =
+ object : NestedScrollConnection {
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ if (
+ velocityOrPositionalThresholdReached(scrollOffset, minScrollOffset, available.y)
+ ) {
+ scrollableState.animateScrollBy(minScrollOffset, tween())
+ } else {
+ scrollableState.animateScrollBy(-minScrollOffset, tween())
+ }
+ return available
+ }
+ }
+
+ LaunchedEffect(isHeadsUp) { scrollOffset = 0f }
+
+ LaunchedEffect(scrollableState.isScrollInProgress) {
+ if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) {
+ viewModel.snoozeHun()
+ }
+ }
+
+ HeadsUpNotificationSpace(
+ stackScrollView = stackScrollView,
+ viewModel = viewModel,
+ modifier =
+ Modifier.absoluteOffset {
+ IntOffset(
+ x = 0,
+ y =
+ calculateHeadsUpPlaceholderYOffset(
+ scrollOffset.roundToInt(),
+ minScrollOffset.roundToInt(),
+ stackScrollView.topHeadsUpHeight
+ )
+ )
+ }
+ .thenIf(isHeadsUp) {
+ Modifier.verticalNestedScrollToScene(
+ bottomBehavior = NestedScrollBehavior.EdgeAlways
+ )
+ .nestedScroll(nestedScrollConnection)
+ .scrollable(
+ orientation = Orientation.Vertical,
+ state = scrollableState,
+ )
+ }
+ )
+}
+
/** Adds the space where notification stack should appear in the scene. */
@Composable
fun SceneScope.ConstrainedNotificationStack(
@@ -480,6 +573,47 @@
}
}
+private fun calculateHeadsUpPlaceholderYOffset(
+ scrollOffset: Int,
+ minScrollOffset: Int,
+ topHeadsUpHeight: Int,
+): Int {
+ return -minScrollOffset +
+ (scrollOffset * (-minScrollOffset + topHeadsUpHeight) / -minScrollOffset)
+}
+
+private fun velocityOrPositionalThresholdReached(
+ scrollOffset: Float,
+ minScrollOffset: Float,
+ availableVelocityY: Float,
+): Boolean {
+ return availableVelocityY < HUN_SNOOZE_VELOCITY_THRESHOLD ||
+ (availableVelocityY <= 0f &&
+ scrollOffset < minScrollOffset * HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION)
+}
+
+/**
+ * Takes a range, current value, and delta, and updates the current value by the delta, coercing the
+ * result within the given range. Returns how much of the delta was consumed.
+ */
+private fun consumeDeltaWithinRange(
+ current: Float,
+ setCurrent: (Float) -> Unit,
+ min: Float,
+ max: Float,
+ delta: Float
+): Float {
+ return if (delta < 0 && current > min) {
+ val remainder = (current + delta - min).coerceAtMost(0f)
+ setCurrent((current + delta).coerceAtLeast(min))
+ delta - remainder
+ } else if (delta > 0 && current < max) {
+ val remainder = (current + delta).coerceAtLeast(0f)
+ setCurrent((current + delta).coerceAtMost(max))
+ delta - remainder
+ } else 0f
+}
+
private inline fun debugLog(
viewModel: NotificationsPlaceholderViewModel,
msg: () -> Any,
@@ -514,3 +648,5 @@
private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f)
private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f)
+private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f
+private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index c066ae5..422c9f6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -16,14 +16,14 @@
package com.android.systemui.qs.ui.composable
-import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -46,6 +46,9 @@
import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileGrid
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.Transitions.QuickSettingsLayoutEnter
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.Transitions.QuickSettingsLayoutExit
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
@@ -95,7 +98,7 @@
)
ShadeBody(
- viewModel = viewModel,
+ viewModel = viewModel.quickSettingsContainerViewModel,
)
}
}
@@ -104,39 +107,30 @@
@Composable
private fun ShadeBody(
- viewModel: QuickSettingsShadeSceneViewModel,
+ viewModel: QuickSettingsContainerViewModel,
) {
val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
- Box {
- // The main Quick Settings grid layout.
- AnimatedVisibility(
- visible = !isEditing,
- enter = QuickSettingsShade.Transitions.QuickSettingsLayoutEnter,
- exit = QuickSettingsShade.Transitions.QuickSettingsLayoutExit,
- ) {
- QuickSettingsLayout(
- viewModel = viewModel,
- )
- }
-
- // The Quick Settings Editor layout.
- AnimatedVisibility(
- visible = isEditing,
- enter = QuickSettingsShade.Transitions.QuickSettingsEditorEnter,
- exit = QuickSettingsShade.Transitions.QuickSettingsEditorExit,
- ) {
+ AnimatedContent(
+ targetState = isEditing,
+ transitionSpec = { QuickSettingsLayoutEnter togetherWith QuickSettingsLayoutExit }
+ ) { editing ->
+ if (editing) {
EditMode(
viewModel = viewModel.editModeViewModel,
modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding)
)
+ } else {
+ QuickSettingsLayout(
+ viewModel = viewModel,
+ )
}
}
}
@Composable
private fun QuickSettingsLayout(
- viewModel: QuickSettingsShadeSceneViewModel,
+ viewModel: QuickSettingsContainerViewModel,
modifier: Modifier = Modifier,
) {
Column(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 4e334c2..a44041a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -17,26 +17,19 @@
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.IntOffset
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
-import com.android.internal.policy.SystemBarUtils
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -72,28 +65,9 @@
)
animateSceneDpAsState(value = Default, key = MediaLandscapeTopOffset, canOverflow = false)
Spacer(modifier.fillMaxSize())
- HeadsUpNotificationStack(
+ SnoozeableHeadsUpNotificationSpace(
stackScrollView = notificationStackScrolLView.get(),
viewModel = notificationsPlaceholderViewModel
)
}
}
-
-@Composable
-private fun SceneScope.HeadsUpNotificationStack(
- stackScrollView: NotificationScrollView,
- viewModel: NotificationsPlaceholderViewModel,
-) {
- val context = LocalContext.current
- val density = LocalDensity.current
- val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
- val headsUpPadding =
- with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
-
- HeadsUpNotificationSpace(
- stackScrollView = stackScrollView,
- viewModel = viewModel,
- modifier =
- Modifier.absoluteOffset { IntOffset(x = 0, y = statusBarHeight + headsUpPadding) }
- )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index fbf91b7..a23bb67 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -48,6 +48,7 @@
import com.android.compose.PlatformSliderColors
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState
@Composable
@@ -62,7 +63,7 @@
val value by valueState(state)
PlatformSlider(
modifier =
- modifier.clearAndSetSemantics {
+ modifier.sysuiResTag(state.label).clearAndSetSemantics {
if (state.isEnabled) {
contentDescription = state.label
state.a11yClickDescription?.let {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index ab14911..9da2a1b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -25,20 +25,25 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.res.R
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+/** Same as android.platform.systemui_tapl.ui.VolumePanel#VolumePanelTestTag */
+private const val VolumePanelTestTag = "VolumePanel"
private val padding = 24.dp
@Composable
+@OptIn(ExperimentalComposeUiApi::class)
fun VolumePanelRoot(
viewModel: VolumePanelViewModel,
modifier: Modifier = Modifier,
@@ -52,6 +57,7 @@
Components(
componentsState,
modifier
+ .sysuiResTag(VolumePanelTestTag)
.semantics { paneTitle = accessibilityTitle }
.padding(
start = padding,
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 2fd7f1b..525839d 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -128,7 +128,7 @@
errorLine1=" mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java"
line="807"
column="65"/>
</issue>
@@ -1646,7 +1646,7 @@
errorLine1=" mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java"
line="177"
column="48"/>
</issue>
@@ -2086,7 +2086,7 @@
errorLine1=" WindowManager wm = getContext().getSystemService(WindowManager.class);"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
line="729"
column="45"/>
</issue>
@@ -2097,7 +2097,7 @@
errorLine1=" WindowManager wm = getContext().getSystemService(WindowManager.class);"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
line="830"
column="41"/>
</issue>
@@ -4049,7 +4049,7 @@
errorLine1=" pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s %f","
errorLine2=" ^">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
line="1128"
column="20"/>
</issue>
@@ -4060,7 +4060,7 @@
errorLine1=" pw.println(String.format(" disabled=0x%08x vertical=%s darkIntensity=%.2f","
errorLine2=" ^">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
line="1134"
column="20"/>
</issue>
@@ -4668,7 +4668,7 @@
errorLine1=" 500).show();"
errorLine2=" ~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java"
line="1894"
column="21"/>
</issue>
@@ -8386,7 +8386,7 @@
errorLine1=" mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java"
line="1500"
column="60"/>
</issue>
@@ -23055,7 +23055,7 @@
errorLine1=" mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
line="1001"
column="48"/>
</issue>
@@ -25002,7 +25002,7 @@
errorLine1=" mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser());"
errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java"
line="373"
column="70"/>
</issue>
@@ -25222,7 +25222,7 @@
errorLine1=" Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);"
errorLine2=" ~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java"
line="329"
column="74"/>
</issue>
@@ -25233,7 +25233,7 @@
errorLine1=" Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);"
errorLine2=" ~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java"
line="357"
column="74"/>
</issue>
@@ -25827,7 +25827,7 @@
errorLine1=" new AsyncTask<Icon, Void, Drawable>() {"
errorLine2=" ^">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java"
line="207"
column="9"/>
</issue>
@@ -29303,7 +29303,7 @@
errorLine1=" public boolean onTouchEvent(MotionEvent ev) {"
errorLine2=" ~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java"
line="267"
column="20"/>
</issue>
@@ -29578,7 +29578,7 @@
errorLine1=" mView.setOnTouchListener(this::onNavigationTouch);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java"
line="782"
column="9"/>
</issue>
@@ -29589,7 +29589,7 @@
errorLine1=" public boolean onTouchEvent(MotionEvent event) {"
errorLine2=" ~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java"
line="383"
column="20"/>
</issue>
@@ -29600,7 +29600,7 @@
errorLine1=" public boolean onTouchEvent(MotionEvent event) {"
errorLine2=" ~~~~~~~~~~~~">
<location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java"
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrame.java"
line="207"
column="20"/>
</issue>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index fa79ea0..54e0725 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1284,6 +1284,41 @@
}
@Test
+ public void onAodDownAndDownTouchReceived() throws RemoteException {
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultDown =
+ new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+ -1 /* pointerId */, touchData);
+
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN fingerprint is requested because of AOD interrupt
+ // GIVEN there's been an AoD interrupt
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+ mScreenObserver.onScreenTurnedOn();
+ mUdfpsController.onAodInterrupt(0, 0, 0, 0);
+ mFgExecutor.runAllReady();
+
+ // and an ACTION_DOWN is received and touch is within sensor
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultDown);
+ MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent);
+ mBiometricExecutor.runAllReady();
+ firstDownEvent.recycle();
+
+ // THEN the touch is only processed once
+ verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
+ anyBoolean());
+ }
+
+ @Test
public void onTouch_pilferPointerWhenAltBouncerShowing()
throws RemoteException {
final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index fbe2c2e..cf14547 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -451,24 +451,6 @@
}
}
- @Test
- fun transitionFromDozingToGlanceableHub_forcesCommunal() =
- with(kosmos) {
- testScope.runTest {
- val scene by collectLastValue(communalSceneInteractor.currentScene)
- communalSceneInteractor.changeScene(CommunalScenes.Blank)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
-
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.DOZING,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope = this
- )
-
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
- }
- }
-
private fun TestScope.updateDocked(docked: Boolean) =
with(kosmos) {
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index da40f64..0169f99 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -185,7 +185,7 @@
fun backgroundType_defaultValue() =
testScope.runTest {
val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
- assertThat(backgroundType).isEqualTo(CommunalBackgroundType.DEFAULT)
+ assertThat(backgroundType).isEqualTo(CommunalBackgroundType.ANIMATED)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 2694cab..d338774 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -69,6 +69,11 @@
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
+import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shade.ShadeTestUtil
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -492,13 +497,16 @@
flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
)
// Transitioned to Glanceable hub.
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope = testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
)
// Shade not expanded.
- shadeTestUtil.setLockscreenShadeExpansion(0f)
+ if (!SceneContainerFlag.isEnabled) shadeTestUtil.setLockscreenShadeExpansion(0f)
assertThat(isFocusable).isEqualTo(true)
}
@@ -541,10 +549,13 @@
keyguardRepository.setKeyguardOccluded(true)
// And on hub
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope = testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
)
// Then flow is not frozen
@@ -558,10 +569,13 @@
assertThat(isCommunalContentFlowFrozen).isEqualTo(true)
// 3. When transitioned to OCCLUDED and activity shows
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED,
- testScope = testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition =
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ )
)
// Then flow is not frozen
@@ -579,10 +593,13 @@
keyguardRepository.setKeyguardOccluded(false)
// And transitioned to hub
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope = testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
)
// Then flow is not frozen
@@ -593,30 +610,30 @@
runCurrent()
// And transitioning to occluded
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED,
- transitionState = TransitionState.STARTED,
- )
- )
-
- keyguardTransitionRepository.sendTransitionStep(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED,
- transitionState = TransitionState.RUNNING,
- value = 0.5f,
+ kosmos.setTransition(
+ sceneTransition = Transition(from = Scenes.Communal, to = Scenes.Lockscreen),
+ stateTransition =
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.STARTED,
+ value = 0f,
+ )
)
// Then flow is frozen
assertThat(isCommunalContentFlowFrozen).isEqualTo(true)
// 3. When transition is finished
- keyguardTransitionRepository.sendTransitionStep(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED,
- transitionState = TransitionState.FINISHED,
- value = 1f,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition =
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
)
// Then flow is not frozen
@@ -639,10 +656,13 @@
keyguardRepository.setKeyguardOccluded(true)
// And transitioned to hub
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope = testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
)
// Widgets available
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c48ced1..86a99e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -149,6 +149,8 @@
when(mDreamOverlayContainerView.getRootSurfaceControl())
.thenReturn(mAttachedSurfaceControl);
when(mKeyguardTransitionInteractor.isFinishedInStateWhere(any())).thenReturn(emptyFlow());
+ when(mKeyguardTransitionInteractor.isFinishedIn(any(), any())).thenReturn(emptyFlow());
+ when(mKeyguardTransitionInteractor.isFinishedIn(any())).thenReturn(emptyFlow());
when(mShadeInteractor.isAnyExpanded()).thenReturn(MutableStateFlow(false));
when(mCommunalInteractor.isCommunalShowing()).thenReturn(MutableStateFlow(false));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 0792a50..612f2e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -34,14 +34,12 @@
import android.os.PowerManager
import android.platform.test.annotations.EnableFlags
-import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
-import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -66,10 +64,8 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
-import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -124,66 +120,6 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testTransitionToLockscreen_onWakeup_canDream_glanceableHubAvailable() =
- testScope.runTest {
- whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
- kosmos.setCommunalAvailable(true)
- runCurrent()
-
- powerInteractor.setAwakeForTest()
- runCurrent()
-
- // If dreaming is possible and communal is available, then we should transition to
- // GLANCEABLE_HUB when waking up.
- assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.GLANCEABLE_HUB,
- )
- }
-
- @Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testTransitionToLockscreen_onWakeup_canNotDream_glanceableHubAvailable() =
- testScope.runTest {
- whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
- kosmos.setCommunalAvailable(true)
- runCurrent()
-
- powerInteractor.setAwakeForTest()
- runCurrent()
-
- // If dreaming is NOT possible but communal is available, then we should transition to
- // LOCKSCREEN when waking up.
- assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.LOCKSCREEN,
- )
- }
-
- @Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testTransitionToLockscreen_onWakeup_canNDream_glanceableHubNotAvailable() =
- testScope.runTest {
- whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
- kosmos.setCommunalAvailable(false)
- runCurrent()
-
- powerInteractor.setAwakeForTest()
- runCurrent()
-
- // If dreaming is possible but communal is NOT available, then we should transition to
- // LOCKSCREEN when waking up.
- assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DOZING,
- to = KeyguardState.LOCKSCREEN,
- )
- }
-
- @Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 0f061de..40918a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -1097,9 +1097,10 @@
}
@Test
+ @DisableSceneContainer
fun isFinishedInState() =
testScope.runTest {
- val results by collectValues(underTest.isFinishedInState(GONE))
+ val results by collectValues(underTest.isFinishedIn(Scenes.Gone, GONE))
sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
@@ -1194,6 +1195,89 @@
}
@Test
+ @EnableSceneContainer
+ fun isFinishedIn() =
+ testScope.runTest {
+ val results by collectValues(underTest.isFinishedIn(Scenes.Gone, GONE))
+
+ sendSteps(
+ TransitionStep(AOD, DOZING, 0f, STARTED),
+ TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+ TransitionStep(AOD, DOZING, 1f, FINISHED),
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false, // Finished in DOZING, not GONE.
+ )
+ )
+
+ kosmos.setSceneTransition(Transition(from = Scenes.Lockscreen, to = Scenes.Gone))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
+
+ kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
+
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
+
+ kosmos.setSceneTransition(Transition(from = Scenes.Lockscreen, to = Scenes.Gone))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
+
+ @Test
fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() =
testScope.runTest {
val finishedStates by collectValues(underTest.finishedKeyguardState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/model/KeyguardStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/model/KeyguardStateTest.kt
new file mode 100644
index 0000000..f1ffe66
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/model/KeyguardStateTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.systemui.keyguard.shared.model
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardStateTest : SysuiTestCase() {
+
+ /**
+ * This test makes sure that the result of [deviceIsAwakeInState] are equal for all the states
+ * that are obsolete with scene container enabled and UNDEFINED. This means for example that if
+ * GONE is transformed to UNDEFINED it makes sure that GONE and UNDEFINED need to have the same
+ * value. This assumption is important as with scene container flag enabled call sites will only
+ * check the result passing in UNDEFINED.
+ *
+ * This is true today, but as more states may become obsolete this assumption may not be true
+ * anymore and therefore [deviceIsAwakeInState] would need to be rewritten to factor in the
+ * scene state.
+ */
+ @Test
+ @EnableSceneContainer
+ fun assertUndefinedResultMatchesObsoleteStateResults() {
+ for (state in KeyguardState.entries) {
+ val isAwakeInSceneContainer =
+ KeyguardState.deviceIsAwakeInState(state.mapToSceneContainerState())
+ val isAwake = KeyguardState.deviceIsAwakeInState(state)
+ assertThat(isAwakeInSceneContainer).isEqualTo(isAwake)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt
new file mode 100644
index 0000000..ae6f576
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.systemui.qs.panels.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QuickQuickSettingsRowRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val underTest = kosmos.quickQuickSettingsRowRepository
+
+ @Test
+ fun rows_followsConfig() =
+ with(kosmos) {
+ testScope.runTest {
+ val rows by collectLastValue(underTest.rows)
+
+ setRowsInConfig(2)
+ assertThat(rows).isEqualTo(2)
+
+ setRowsInConfig(3)
+ assertThat(rows).isEqualTo(3)
+ }
+ }
+
+ private fun setRowsInConfig(rows: Int) =
+ with(kosmos) {
+ testCase.context.orCreateTestableResources.addOverride(
+ R.integer.quick_qs_panel_max_rows,
+ rows,
+ )
+ fakeConfigurationRepository.onConfigurationChange()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
new file mode 100644
index 0000000..d36a81b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.systemui.qs.panels.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QuickQuickSettingsViewModelTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ iconTilesRepository =
+ object : IconTilesRepository {
+ override fun isIconTile(spec: TileSpec): Boolean {
+ return spec.spec.startsWith(PREFIX_SMALL)
+ }
+ }
+ }
+
+ private val underTest = kosmos.quickQuickSettingsViewModel
+
+ private val tiles =
+ listOf(
+ "$PREFIX_SMALL:1",
+ "$PREFIX_SMALL:2",
+ "$PREFIX_LARGE:3",
+ "$PREFIX_SMALL:4",
+ "$PREFIX_LARGE:5",
+ "$PREFIX_LARGE:6",
+ "$PREFIX_SMALL:7",
+ "$PREFIX_SMALL:8",
+ "$PREFIX_LARGE:9",
+ )
+ .map(TileSpec::create)
+
+ @Before
+ fun setUp() {
+ kosmos.setTiles(tiles)
+ }
+
+ @Test
+ fun splitIntoRows_onlyFirstTwoRowsOfTiles() =
+ with(kosmos) {
+ testScope.runTest {
+ setRows(2)
+ val columns by collectLastValue(underTest.columns)
+ val tileViewModels by collectLastValue(underTest.tileViewModels)
+
+ assertThat(columns).isEqualTo(4)
+ // All tiles in 4 columns
+ // [1] [2] [3 3]
+ // [4] [5 5]
+ // [6 6] [7] [8]
+ // [9 9]
+
+ assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(5))
+ }
+ }
+
+ @Test
+ fun changeRows_tilesChange() =
+ with(kosmos) {
+ testScope.runTest {
+ setRows(2)
+ val columns by collectLastValue(underTest.columns)
+ val tileViewModels by collectLastValue(underTest.tileViewModels)
+
+ assertThat(columns).isEqualTo(4)
+ // All tiles in 4 columns
+ // [1] [2] [3 3]
+ // [4] [5 5]
+ // [6 6] [7] [8]
+ // [9 9]
+
+ setRows(3)
+ assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(8))
+ setRows(1)
+ assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(3))
+ }
+ }
+
+ @Test
+ fun changeTiles_tilesChange() =
+ with(kosmos) {
+ testScope.runTest {
+ setRows(2)
+ val columns by collectLastValue(underTest.columns)
+ val tileViewModels by collectLastValue(underTest.tileViewModels)
+
+ assertThat(columns).isEqualTo(4)
+ // All tiles in 4 columns
+ // [1] [2] [3 3]
+ // [4] [5 5]
+ // [6 6] [7] [8]
+ // [9 9]
+
+ // Remove tile small:4
+ currentTilesInteractor.removeTiles(setOf(tiles[3]))
+
+ assertThat(tileViewModels!!.map { it.tile.spec })
+ .isEqualTo(
+ listOf(
+ "$PREFIX_SMALL:1",
+ "$PREFIX_SMALL:2",
+ "$PREFIX_LARGE:3",
+ "$PREFIX_LARGE:5",
+ "$PREFIX_LARGE:6",
+ )
+ .map(TileSpec::create)
+ )
+ }
+ }
+
+ private fun Kosmos.setTiles(tiles: List<TileSpec>) {
+ currentTilesInteractor.setTiles(tiles)
+ }
+
+ private fun Kosmos.setRows(rows: Int) {
+ testCase.context.orCreateTestableResources.addOverride(
+ R.integer.quick_qs_panel_max_rows,
+ rows,
+ )
+ fakeConfigurationRepository.onConfigurationChange()
+ }
+
+ private companion object {
+ const val PREFIX_SMALL = "small"
+ const val PREFIX_LARGE = "large"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
index 411a7a4..a7a3a0f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
@@ -19,6 +19,7 @@
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -31,12 +32,12 @@
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.fakeShadeRepository
-import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -155,6 +156,24 @@
assertThat(homeScene).isEqualTo(Scenes.Gone)
}
+ @Test
+ fun backTransitionSceneKey_notEditing_Home() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+
+ assertThat(destinationScenes?.get(Back)?.toScene).isEqualTo(SceneFamilies.Home)
+ }
+
+ @Test
+ fun backTransition_editing_noDestination() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ kosmos.editModeViewModel.startEditing()
+
+ assertThat(destinationScenes!!).isNotEmpty()
+ assertThat(destinationScenes?.get(Back)).isNull()
+ }
+
private fun TestScope.lockDevice() {
val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index e73cae2..355669b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -38,8 +38,11 @@
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -392,11 +395,15 @@
)
assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(true)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- testScope = testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
)
+
assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false)
}
}
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
index fd06238..14751ef 100644
--- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -31,7 +31,6 @@
android:id="@*android:id/contentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="48dp"
android:paddingStart="@dimen/dialog_side_padding"
android:paddingEnd="@dimen/dialog_side_padding"
>
diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml
index 046aecd..acefaae 100644
--- a/packages/SystemUI/res/layout/back.xml
+++ b/packages/SystemUI/res/layout/back.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/back"
diff --git a/packages/SystemUI/res/layout/contextual.xml b/packages/SystemUI/res/layout/contextual.xml
index 2cd7926..ac840d8 100644
--- a/packages/SystemUI/res/layout/contextual.xml
+++ b/packages/SystemUI/res/layout/contextual.xml
@@ -24,7 +24,7 @@
android:clipChildren="false"
android:clipToPadding="false"
>
- <com.android.systemui.navigationbar.buttons.KeyButtonView
+ <com.android.systemui.navigationbar.views.buttons.KeyButtonView
android:id="@+id/menu"
android:layout_height="match_parent"
android:layout_width="match_parent"
@@ -47,7 +47,7 @@
android:layout_height="match_parent"
android:visibility="invisible"
/>
- <com.android.systemui.navigationbar.buttons.KeyButtonView
+ <com.android.systemui.navigationbar.views.buttons.KeyButtonView
android:id="@+id/accessibility_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/custom_key.xml b/packages/SystemUI/res/layout/custom_key.xml
index dc65777..0b0f4fa 100644
--- a/packages/SystemUI/res/layout/custom_key.xml
+++ b/packages/SystemUI/res/layout/custom_key.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/navigation_side_padding"
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
index 84eed6a..227e390 100644
--- a/packages/SystemUI/res/layout/home.xml
+++ b/packages/SystemUI/res/layout/home.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/home"
diff --git a/packages/SystemUI/res/layout/ime_switcher.xml b/packages/SystemUI/res/layout/ime_switcher.xml
index a2c8308..fa9a12b 100644
--- a/packages/SystemUI/res/layout/ime_switcher.xml
+++ b/packages/SystemUI/res/layout/ime_switcher.xml
@@ -15,7 +15,7 @@
~ limitations under the License
-->
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ime_switcher"
android:layout_width="@dimen/navigation_key_width"
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
index 0bb622b..443e4e3 100644
--- a/packages/SystemUI/res/layout/menu_ime.xml
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -25,7 +25,7 @@
are placed inside a view that has a size controlled by weight. Ensure weight is large enough to
support icon size. Use layout_width=navigation_side_padding like other navbar buttons. -->
- <com.android.systemui.navigationbar.buttons.KeyButtonView
+ <com.android.systemui.navigationbar.views.buttons.KeyButtonView
android:id="@+id/menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -43,7 +43,7 @@
android:paddingStart="0dp"
android:paddingEnd="0dp"
/>
- <com.android.systemui.navigationbar.buttons.KeyButtonView
+ <com.android.systemui.navigationbar.views.buttons.KeyButtonView
android:id="@+id/rotate_suggestion"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -51,7 +51,7 @@
android:scaleType="centerInside"
android:contentDescription="@string/accessibility_rotate_button"
/>
- <com.android.systemui.navigationbar.buttons.KeyButtonView
+ <com.android.systemui.navigationbar.views.buttons.KeyButtonView
android:id="@+id/accessibility_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index 5f59e78..a868ec4 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -17,7 +17,7 @@
*/
-->
-<com.android.systemui.navigationbar.NavigationBarView
+<com.android.systemui.navigationbar.views.NavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/navigation_bar_view"
android:layout_height="match_parent"
@@ -26,11 +26,11 @@
android:clipToPadding="false"
android:background="@drawable/system_bar_background">
- <com.android.systemui.navigationbar.NavigationBarInflaterView
+ <com.android.systemui.navigationbar.views.NavigationBarInflaterView
android:id="@+id/navigation_inflater"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false" />
-</com.android.systemui.navigationbar.NavigationBarView>
+</com.android.systemui.navigationbar.views.NavigationBarView>
diff --git a/packages/SystemUI/res/layout/navigation_bar_window.xml b/packages/SystemUI/res/layout/navigation_bar_window.xml
index b2473cd..9442124 100644
--- a/packages/SystemUI/res/layout/navigation_bar_window.xml
+++ b/packages/SystemUI/res/layout/navigation_bar_window.xml
@@ -16,7 +16,7 @@
** limitations under the License.
*/
-->
-<com.android.systemui.navigationbar.NavigationBarFrame
+<com.android.systemui.navigationbar.views.NavigationBarFrame
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_bar_frame"
@@ -24,4 +24,4 @@
android:layout_height="match_parent"
android:layout_width="match_parent">
-</com.android.systemui.navigationbar.NavigationBarFrame>
+</com.android.systemui.navigationbar.views.NavigationBarFrame>
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
index 46f238c..5c6c9a4 100644
--- a/packages/SystemUI/res/layout/navigation_layout.xml
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -27,7 +27,7 @@
android:clipToPadding="false"
android:id="@+id/horizontal">
- <com.android.systemui.navigationbar.buttons.NearestTouchFrame
+ <com.android.systemui.navigationbar.views.buttons.NearestTouchFrame
android:id="@+id/nav_buttons"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -53,6 +53,6 @@
android:clipToPadding="false"
android:clipChildren="false" />
- </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
+ </com.android.systemui.navigationbar.views.buttons.NearestTouchFrame>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout_vertical.xml b/packages/SystemUI/res/layout/navigation_layout_vertical.xml
index 42e9324..c64e3f7 100644
--- a/packages/SystemUI/res/layout/navigation_layout_vertical.xml
+++ b/packages/SystemUI/res/layout/navigation_layout_vertical.xml
@@ -25,7 +25,7 @@
android:paddingBottom="@dimen/nav_content_padding"
android:id="@+id/vertical">
- <com.android.systemui.navigationbar.buttons.NearestTouchFrame
+ <com.android.systemui.navigationbar.views.buttons.NearestTouchFrame
android:id="@+id/nav_buttons"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -33,7 +33,7 @@
android:clipToPadding="false"
systemui:isVertical="true">
- <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
+ <com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout
android:id="@+id/ends_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -41,7 +41,7 @@
android:clipToPadding="false"
android:clipChildren="false" />
- <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
+ <com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout
android:id="@+id/center_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -50,6 +50,6 @@
android:clipToPadding="false"
android:clipChildren="false" />
- </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
+ </com.android.systemui.navigationbar.views.buttons.NearestTouchFrame>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml
index e2b1374..767e16c 100644
--- a/packages/SystemUI/res/layout/recent_apps.xml
+++ b/packages/SystemUI/res/layout/recent_apps.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<com.android.systemui.navigationbar.buttons.KeyButtonView
+<com.android.systemui.navigationbar.views.buttons.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/recent_apps"
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 177ba598..212dae2 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -233,6 +233,7 @@
<item type="id" name="smart_space_barrier_bottom" />
<item type="id" name="small_clock_guideline_top" />
<item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
+ <item type="id" name="weather_clock_bc_smartspace_bottom" />
<item type="id" name="accessibility_actions_view" />
<!-- Privacy dialog -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 4af366c..0f11717 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -39,7 +39,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
index 0cdb376..7dfa4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
@@ -31,9 +31,9 @@
import android.view.View;
import com.android.settingslib.Utils;
-import com.android.systemui.navigationbar.NavigationBar;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarTransitions;
+import com.android.systemui.navigationbar.views.NavigationBar;
+import com.android.systemui.navigationbar.views.NavigationBarTransitions;
import com.android.systemui.res.R;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index ad142a8..9d3c6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -558,7 +558,12 @@
Log.w(TAG, "onTouch down received without a preceding up");
}
mActivePointerId = MotionEvent.INVALID_POINTER_ID;
- mOnFingerDown = false;
+
+ // It's possible on some devices to get duplicate touches from both doze and the
+ // normal touch listener. Don't reset the down in this case to avoid duplicate downs
+ if (!mIsAodInterruptActive) {
+ mOnFingerDown = false;
+ }
} else if (!DeviceEntryUdfpsRefactor.isEnabled()) {
if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
&& !mAlternateBouncerInteractor.isVisibleState())
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index e31f1ad..88c3f9f6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -18,7 +18,6 @@
import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -92,8 +91,8 @@
keyguardTransitionInteractor.startedKeyguardTransitionStep
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
- .onEach { (nextScene, nextTransition) ->
- communalSceneInteractor.changeScene(nextScene, nextTransition)
+ .onEach { nextScene ->
+ communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade)
}
.launchIn(applicationScope)
@@ -189,7 +188,7 @@
private suspend fun determineSceneAfterTransition(
lastStartedTransition: TransitionStep,
- ): Pair<SceneKey, TransitionKey>? {
+ ): SceneKey? {
val to = lastStartedTransition.to
val from = lastStartedTransition.from
val docked = dockManager.isDocked
@@ -202,27 +201,22 @@
// underneath the hub is shown. When launching activities over lockscreen, we only
// change scenes once the activity launch animation is finished, so avoid
// changing the scene here.
- Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+ CommunalScenes.Blank
}
to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
// When transitioning to the hub from an occluded state, fade out the hub without
// doing any translation.
- Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
+ CommunalScenes.Communal
}
// Transitioning to Blank scene when entering the edit mode will be handled separately
// with custom animations.
to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
- Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+ CommunalScenes.Blank
!docked && !KeyguardState.deviceIsAwakeInState(to) -> {
// If the user taps the screen and wakes the device within this timeout, we don't
// want to dismiss the hub
delay(AWAKE_DEBOUNCE_DELAY)
- Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
- }
- from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
- // Make sure the communal hub is showing (immediately, not fading in) when
- // transitioning from dozing to hub.
- Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
+ CommunalScenes.Blank
}
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 2940a95..e3ef6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -115,11 +115,11 @@
val intType =
secureSettings.getIntForUser(
GLANCEABLE_HUB_BACKGROUND_SETTING,
- CommunalBackgroundType.DEFAULT.value,
+ CommunalBackgroundType.ANIMATED.value,
user.id
)
CommunalBackgroundType.entries.find { type -> type.value == intType }
- ?: CommunalBackgroundType.DEFAULT
+ ?: CommunalBackgroundType.ANIMATED
}
private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
index 4eaba06..3b23ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
@@ -18,7 +18,7 @@
/** Models the types of background that can be shown on the hub. */
enum class CommunalBackgroundType(val value: Int) {
- DEFAULT(0),
+ STATIC(0),
STATIC_GRADIENT(1),
ANIMATED(2),
NONE(3),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index fab2435..2e92438 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -93,7 +93,7 @@
*/
val canShowEditMode =
allOf(
- keyguardTransitionInteractor.isFinishedInState(KeyguardState.GONE),
+ keyguardTransitionInteractor.isFinishedIn(KeyguardState.GONE),
communalInteractor.editModeOpen
)
.filter { it }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 0466bbc..f9f01f7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -40,6 +40,7 @@
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
@@ -144,7 +145,10 @@
*/
override val isCommunalContentFlowFrozen: Flow<Boolean> =
allOf(
- keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+ keyguardTransitionInteractor.isFinishedIn(
+ scene = Scenes.Communal,
+ stateWithoutSceneContainer = KeyguardState.GLANCEABLE_HUB
+ ),
keyguardInteractor.isKeyguardOccluded,
not(keyguardInteractor.isAbleToDream)
)
@@ -177,7 +181,10 @@
// opened.
override val isFocusable: Flow<Boolean> =
combine(
- keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+ keyguardTransitionInteractor.isFinishedIn(
+ scene = Scenes.Communal,
+ stateWithoutSceneContainer = KeyguardState.GLANCEABLE_HUB
+ ),
communalInteractor.isIdleOnCommunal,
shadeInteractor.isAnyFullyExpanded,
) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded ->
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
index 1e04fe7..4217744 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
@@ -56,7 +56,7 @@
Color.valueOf(
Utils.getColorAttrDefaultColor(
context,
- com.android.internal.R.attr.materialColorOutlineVariant
+ com.android.internal.R.attr.materialColorPrimary
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 813fccf..17202639 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -58,6 +58,7 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -221,8 +222,7 @@
trySendWithFailureLogging(it.bypassEnabled, TAG, "BypassStateChanged")
awaitClose { it.unregisterOnBypassStateChangedListener(callback) }
}
- }
- ?: flowOf(false)
+ } ?: flowOf(false)
override fun setLockedOut(isLockedOut: Boolean) {
_isLockedOut.value = isLockedOut
@@ -322,7 +322,10 @@
merge(
powerInteractor.isAsleep,
combine(
- keyguardTransitionInteractor.isFinishedInState(KeyguardState.GONE),
+ keyguardTransitionInteractor.isFinishedIn(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE
+ ),
keyguardInteractor.statusBarState,
) { isFinishedInGoneState, statusBarState ->
// When the user is dragging the primary bouncer in (up) by manually scrolling
@@ -733,6 +736,7 @@
pw.println(" lockscreenBypassEnabled: ${keyguardBypassController?.bypassEnabled ?: false}")
}
}
+
/** Combine two boolean flows by and-ing both of them */
private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
flow.combine(anotherFlow) { a, b -> a && b }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 245def8..4fd1c24 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -53,6 +53,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.BlurUtils;
@@ -274,13 +275,19 @@
collectFlow(
mView,
FlowKt.distinctUntilChanged(combineFlows(
- mKeyguardTransitionInteractor.isFinishedInStateWhere(
- KeyguardState::isBouncerState),
+ mKeyguardTransitionInteractor.isFinishedIn(
+ Scenes.Bouncer, KeyguardState.PRIMARY_BOUNCER),
+ mKeyguardTransitionInteractor.isFinishedIn(
+ KeyguardState.ALTERNATE_BOUNCER),
mShadeInteractor.isAnyExpanded(),
mCommunalInteractor.isCommunalShowing(),
- (anyBouncerShowing, shadeExpanded, communalShowing) -> {
- mAnyBouncerShowing = anyBouncerShowing;
- return anyBouncerShowing || shadeExpanded || communalShowing;
+ (primaryBouncerShowing,
+ alternateBouncerShowing,
+ shadeExpanded,
+ communalShowing) -> {
+ mAnyBouncerShowing = primaryBouncerShowing
+ || alternateBouncerShowing;
+ return mAnyBouncerShowing || shadeExpanded || communalShowing;
})),
mDreamManager::setDreamIsObscured,
mBackgroundDispatcher);
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
new file mode 100644
index 0000000..0aced8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -0,0 +1,351 @@
+/*
+ * 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.systemui.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.view.KeyEvent.KEYCODE_ALT_LEFT
+import android.view.KeyEvent.KEYCODE_ALT_RIGHT
+import android.view.KeyEvent.KEYCODE_BACK
+import android.view.KeyEvent.KEYCODE_BREAK
+import android.view.KeyEvent.KEYCODE_BUTTON_A
+import android.view.KeyEvent.KEYCODE_BUTTON_B
+import android.view.KeyEvent.KEYCODE_BUTTON_C
+import android.view.KeyEvent.KEYCODE_BUTTON_L1
+import android.view.KeyEvent.KEYCODE_BUTTON_L2
+import android.view.KeyEvent.KEYCODE_BUTTON_MODE
+import android.view.KeyEvent.KEYCODE_BUTTON_R1
+import android.view.KeyEvent.KEYCODE_BUTTON_R2
+import android.view.KeyEvent.KEYCODE_BUTTON_SELECT
+import android.view.KeyEvent.KEYCODE_BUTTON_START
+import android.view.KeyEvent.KEYCODE_BUTTON_X
+import android.view.KeyEvent.KEYCODE_BUTTON_Y
+import android.view.KeyEvent.KEYCODE_BUTTON_Z
+import android.view.KeyEvent.KEYCODE_CTRL_LEFT
+import android.view.KeyEvent.KEYCODE_CTRL_RIGHT
+import android.view.KeyEvent.KEYCODE_DEL
+import android.view.KeyEvent.KEYCODE_DPAD_CENTER
+import android.view.KeyEvent.KEYCODE_DPAD_DOWN
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
+import android.view.KeyEvent.KEYCODE_DPAD_UP
+import android.view.KeyEvent.KEYCODE_EISU
+import android.view.KeyEvent.KEYCODE_ENTER
+import android.view.KeyEvent.KEYCODE_EQUALS
+import android.view.KeyEvent.KEYCODE_ESCAPE
+import android.view.KeyEvent.KEYCODE_F1
+import android.view.KeyEvent.KEYCODE_F10
+import android.view.KeyEvent.KEYCODE_F11
+import android.view.KeyEvent.KEYCODE_F12
+import android.view.KeyEvent.KEYCODE_F2
+import android.view.KeyEvent.KEYCODE_F3
+import android.view.KeyEvent.KEYCODE_F4
+import android.view.KeyEvent.KEYCODE_F5
+import android.view.KeyEvent.KEYCODE_F6
+import android.view.KeyEvent.KEYCODE_F7
+import android.view.KeyEvent.KEYCODE_F8
+import android.view.KeyEvent.KEYCODE_F9
+import android.view.KeyEvent.KEYCODE_FORWARD_DEL
+import android.view.KeyEvent.KEYCODE_GRAVE
+import android.view.KeyEvent.KEYCODE_HENKAN
+import android.view.KeyEvent.KEYCODE_HOME
+import android.view.KeyEvent.KEYCODE_INSERT
+import android.view.KeyEvent.KEYCODE_KATAKANA_HIRAGANA
+import android.view.KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
+import android.view.KeyEvent.KEYCODE_MEDIA_NEXT
+import android.view.KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+import android.view.KeyEvent.KEYCODE_MEDIA_PREVIOUS
+import android.view.KeyEvent.KEYCODE_MEDIA_REWIND
+import android.view.KeyEvent.KEYCODE_MEDIA_STOP
+import android.view.KeyEvent.KEYCODE_MINUS
+import android.view.KeyEvent.KEYCODE_MOVE_END
+import android.view.KeyEvent.KEYCODE_MOVE_HOME
+import android.view.KeyEvent.KEYCODE_MUHENKAN
+import android.view.KeyEvent.KEYCODE_NUMPAD_0
+import android.view.KeyEvent.KEYCODE_NUMPAD_1
+import android.view.KeyEvent.KEYCODE_NUMPAD_2
+import android.view.KeyEvent.KEYCODE_NUMPAD_3
+import android.view.KeyEvent.KEYCODE_NUMPAD_4
+import android.view.KeyEvent.KEYCODE_NUMPAD_5
+import android.view.KeyEvent.KEYCODE_NUMPAD_6
+import android.view.KeyEvent.KEYCODE_NUMPAD_7
+import android.view.KeyEvent.KEYCODE_NUMPAD_8
+import android.view.KeyEvent.KEYCODE_NUMPAD_9
+import android.view.KeyEvent.KEYCODE_NUMPAD_ADD
+import android.view.KeyEvent.KEYCODE_NUMPAD_COMMA
+import android.view.KeyEvent.KEYCODE_NUMPAD_DIVIDE
+import android.view.KeyEvent.KEYCODE_NUMPAD_DOT
+import android.view.KeyEvent.KEYCODE_NUMPAD_ENTER
+import android.view.KeyEvent.KEYCODE_NUMPAD_EQUALS
+import android.view.KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN
+import android.view.KeyEvent.KEYCODE_NUMPAD_MULTIPLY
+import android.view.KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN
+import android.view.KeyEvent.KEYCODE_NUMPAD_SUBTRACT
+import android.view.KeyEvent.KEYCODE_NUM_LOCK
+import android.view.KeyEvent.KEYCODE_PAGE_DOWN
+import android.view.KeyEvent.KEYCODE_PAGE_UP
+import android.view.KeyEvent.KEYCODE_PERIOD
+import android.view.KeyEvent.KEYCODE_SCROLL_LOCK
+import android.view.KeyEvent.KEYCODE_SHIFT_LEFT
+import android.view.KeyEvent.KEYCODE_SHIFT_RIGHT
+import android.view.KeyEvent.KEYCODE_SPACE
+import android.view.KeyEvent.KEYCODE_SYSRQ
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.KEYCODE_ZENKAKU_HANKAKU
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CTRL_ON
+import android.view.KeyEvent.META_FUNCTION_ON
+import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import android.view.KeyEvent.META_SYM_ON
+import com.android.systemui.res.R
+
+object ShortcutHelperKeys {
+
+ val keyIcons =
+ mapOf(
+ META_META_ON to R.drawable.ic_ksh_key_meta,
+ )
+
+ val specialKeyLabels =
+ mapOf<Int, (Context) -> String>(
+ KEYCODE_HOME to { context -> context.getString(R.string.keyboard_key_home) },
+ KEYCODE_BACK to { context -> context.getString(R.string.keyboard_key_back) },
+ KEYCODE_DPAD_UP to { context -> context.getString(R.string.keyboard_key_dpad_up) },
+ KEYCODE_DPAD_DOWN to { context -> context.getString(R.string.keyboard_key_dpad_down) },
+ KEYCODE_DPAD_LEFT to { context -> context.getString(R.string.keyboard_key_dpad_left) },
+ KEYCODE_DPAD_RIGHT to
+ { context ->
+ context.getString(R.string.keyboard_key_dpad_right)
+ },
+ KEYCODE_DPAD_CENTER to
+ { context ->
+ context.getString(R.string.keyboard_key_dpad_center)
+ },
+ KEYCODE_PERIOD to { "." },
+ KEYCODE_TAB to { context -> context.getString(R.string.keyboard_key_tab) },
+ KEYCODE_SPACE to { context -> context.getString(R.string.keyboard_key_space) },
+ KEYCODE_ENTER to { context -> context.getString(R.string.keyboard_key_enter) },
+ KEYCODE_DEL to { context -> context.getString(R.string.keyboard_key_backspace) },
+ KEYCODE_MEDIA_PLAY_PAUSE to
+ { context ->
+ context.getString(R.string.keyboard_key_media_play_pause)
+ },
+ KEYCODE_MEDIA_STOP to
+ { context ->
+ context.getString(R.string.keyboard_key_media_stop)
+ },
+ KEYCODE_MEDIA_NEXT to
+ { context ->
+ context.getString(R.string.keyboard_key_media_next)
+ },
+ KEYCODE_MEDIA_PREVIOUS to
+ { context ->
+ context.getString(R.string.keyboard_key_media_previous)
+ },
+ KEYCODE_MEDIA_REWIND to
+ { context ->
+ context.getString(R.string.keyboard_key_media_rewind)
+ },
+ KEYCODE_MEDIA_FAST_FORWARD to
+ { context ->
+ context.getString(R.string.keyboard_key_media_fast_forward)
+ },
+ KEYCODE_PAGE_UP to { context -> context.getString(R.string.keyboard_key_page_up) },
+ KEYCODE_PAGE_DOWN to { context -> context.getString(R.string.keyboard_key_page_down) },
+ KEYCODE_BUTTON_A to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "A")
+ },
+ KEYCODE_BUTTON_B to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "B")
+ },
+ KEYCODE_BUTTON_C to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "C")
+ },
+ KEYCODE_BUTTON_X to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "X")
+ },
+ KEYCODE_BUTTON_Y to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "Y")
+ },
+ KEYCODE_BUTTON_Z to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "Z")
+ },
+ KEYCODE_BUTTON_L1 to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "L1")
+ },
+ KEYCODE_BUTTON_R1 to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "R1")
+ },
+ KEYCODE_BUTTON_L2 to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "L2")
+ },
+ KEYCODE_BUTTON_R2 to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "R2")
+ },
+ KEYCODE_BUTTON_START to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "Start")
+ },
+ KEYCODE_BUTTON_SELECT to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "Select")
+ },
+ KEYCODE_BUTTON_MODE to
+ { context ->
+ context.getString(R.string.keyboard_key_button_template, "Mode")
+ },
+ KEYCODE_FORWARD_DEL to
+ { context ->
+ context.getString(R.string.keyboard_key_forward_del)
+ },
+ KEYCODE_ESCAPE to { "Esc" },
+ KEYCODE_SYSRQ to { "SysRq" },
+ KEYCODE_BREAK to { "Break" },
+ KEYCODE_SCROLL_LOCK to { "Scroll Lock" },
+ KEYCODE_MOVE_HOME to { context -> context.getString(R.string.keyboard_key_move_home) },
+ KEYCODE_MOVE_END to { context -> context.getString(R.string.keyboard_key_move_end) },
+ KEYCODE_INSERT to { context -> context.getString(R.string.keyboard_key_insert) },
+ KEYCODE_F1 to { "F1" },
+ KEYCODE_F2 to { "F2" },
+ KEYCODE_F3 to { "F3" },
+ KEYCODE_F4 to { "F4" },
+ KEYCODE_F5 to { "F5" },
+ KEYCODE_F6 to { "F6" },
+ KEYCODE_F7 to { "F7" },
+ KEYCODE_F8 to { "F8" },
+ KEYCODE_F9 to { "F9" },
+ KEYCODE_F10 to { "F10" },
+ KEYCODE_F11 to { "F11" },
+ KEYCODE_F12 to { "F12" },
+ KEYCODE_NUM_LOCK to { context -> context.getString(R.string.keyboard_key_num_lock) },
+ KEYCODE_MINUS to { "-" },
+ KEYCODE_GRAVE to { "`" },
+ KEYCODE_EQUALS to { "=" },
+ KEYCODE_NUMPAD_0 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "0")
+ },
+ KEYCODE_NUMPAD_1 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "1")
+ },
+ KEYCODE_NUMPAD_2 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "2")
+ },
+ KEYCODE_NUMPAD_3 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "3")
+ },
+ KEYCODE_NUMPAD_4 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "4")
+ },
+ KEYCODE_NUMPAD_5 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "5")
+ },
+ KEYCODE_NUMPAD_6 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "6")
+ },
+ KEYCODE_NUMPAD_7 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "7")
+ },
+ KEYCODE_NUMPAD_8 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "8")
+ },
+ KEYCODE_NUMPAD_9 to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "9")
+ },
+ KEYCODE_NUMPAD_DIVIDE to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "/")
+ },
+ KEYCODE_NUMPAD_MULTIPLY to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "*")
+ },
+ KEYCODE_NUMPAD_SUBTRACT to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "-")
+ },
+ KEYCODE_NUMPAD_ADD to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "+")
+ },
+ KEYCODE_NUMPAD_DOT to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, ".")
+ },
+ KEYCODE_NUMPAD_COMMA to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, ",")
+ },
+ KEYCODE_NUMPAD_ENTER to
+ { context ->
+ context.getString(
+ R.string.keyboard_key_numpad_template,
+ context.getString(R.string.keyboard_key_enter)
+ )
+ },
+ KEYCODE_NUMPAD_EQUALS to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "=")
+ },
+ KEYCODE_NUMPAD_LEFT_PAREN to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, "(")
+ },
+ KEYCODE_NUMPAD_RIGHT_PAREN to
+ { context ->
+ context.getString(R.string.keyboard_key_numpad_template, ")")
+ },
+ KEYCODE_ZENKAKU_HANKAKU to { "半角/全角" },
+ KEYCODE_EISU to { "英数" },
+ KEYCODE_MUHENKAN to { "無変換" },
+ KEYCODE_HENKAN to { "変換" },
+ KEYCODE_KATAKANA_HIRAGANA to { "かな" },
+ KEYCODE_ALT_LEFT to { "Alt" },
+ KEYCODE_ALT_RIGHT to { "Alt" },
+ KEYCODE_CTRL_LEFT to { "Ctrl" },
+ KEYCODE_CTRL_RIGHT to { "Ctrl" },
+ KEYCODE_SHIFT_LEFT to { "Shift" },
+ KEYCODE_SHIFT_RIGHT to { "Shift" },
+
+ // Modifiers
+ META_META_ON to { "Meta" },
+ META_CTRL_ON to { "Ctrl" },
+ META_ALT_ON to { "Alt" },
+ META_SHIFT_ON to { "Shift" },
+ META_SYM_ON to { "Sym" },
+ META_FUNCTION_ON to { "Fn" },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 506a18d..b423ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -17,10 +17,8 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -53,10 +51,8 @@
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
- private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
- private val dreamManager: DreamManager,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
@@ -123,8 +119,7 @@
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
keyguardInteractor.isKeyguardOccluded,
- communalInteractor.isCommunalAvailable,
- communalSceneInteractor.isIdleOnCommunal,
+ communalInteractor.isIdleOnCommunal,
canTransitionToGoneOnWake,
keyguardInteractor.primaryBouncerShowing,
)
@@ -132,7 +127,6 @@
(
_,
occluded,
- isCommunalAvailable,
isIdleOnCommunal,
canTransitionToGoneOnWake,
primaryBouncerShowing) ->
@@ -147,10 +141,6 @@
KeyguardState.OCCLUDED
} else if (isIdleOnCommunal) {
KeyguardState.GLANCEABLE_HUB
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
- KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
}
@@ -169,8 +159,7 @@
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
.sample(
- communalInteractor.isCommunalAvailable,
- communalSceneInteractor.isIdleOnCommunal,
+ communalInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
canTransitionToGoneOnWake,
keyguardInteractor.primaryBouncerShowing,
@@ -178,7 +167,6 @@
.collect {
(
_,
- isCommunalAvailable,
isIdleOnCommunal,
biometricUnlockState,
canDismissLockscreen,
@@ -200,10 +188,6 @@
KeyguardState.PRIMARY_BOUNCER
} else if (isIdleOnCommunal) {
KeyguardState.GLANCEABLE_HUB
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
- KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
},
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 37272dc..2d389aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -530,17 +530,23 @@
return finishedKeyguardState.map { stateMatcher(it) }.distinctUntilChanged()
}
- /** Whether we've FINISHED a transition to a state that matches the given predicate. */
- fun isFinishedInState(state: KeyguardState): Flow<Boolean> {
- return finishedKeyguardState.map { it == state }.distinctUntilChanged()
+ fun isFinishedIn(scene: SceneKey, stateWithoutSceneContainer: KeyguardState): Flow<Boolean> {
+ return if (SceneContainerFlag.isEnabled) {
+ sceneInteractor
+ .get()
+ .transitionState
+ .map { it.isIdle(scene) || it.isTransitioning(from = scene) }
+ .distinctUntilChanged()
+ } else {
+ isFinishedIn(stateWithoutSceneContainer)
+ }
}
- /**
- * Whether we've FINISHED a transition to a state that matches the given predicate. Consider
- * using [isFinishedInStateWhere] whenever possible instead
- */
- fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) =
- stateMatcher(finishedKeyguardState.replayCache.last())
+ /** Whether we've FINISHED a transition to a state */
+ fun isFinishedIn(state: KeyguardState): Flow<Boolean> {
+ state.checkValidState()
+ return finishedKeyguardState.map { it == state }.distinctUntilChanged()
+ }
fun getCurrentState(): KeyguardState {
return currentKeyguardState.replayCache.last()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 6a2bb5f..8db6a43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -15,7 +15,9 @@
*/
package com.android.systemui.keyguard.shared.model
+import android.util.Log
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
/** List of all possible states to transition to/from */
@@ -87,6 +89,22 @@
/** An activity is displaying over the keyguard. */
OCCLUDED;
+ fun checkValidState() {
+ val isStateValid: Boolean
+ val isEnabled: String
+ if (SceneContainerFlag.isEnabled) {
+ isStateValid = this === mapToSceneContainerState()
+ isEnabled = "enabled"
+ } else {
+ isStateValid = this !== UNDEFINED
+ isEnabled = "disabled"
+ }
+
+ if (!isStateValid) {
+ Log.e("KeyguardState", "$this is not a valid state when scene container is $isEnabled")
+ }
+ }
+
fun mapToSceneContainerState(): KeyguardState {
return when (this) {
OFF,
@@ -128,17 +146,12 @@
return state != GONE
}
- /** Whether either of the bouncers are visible when we're FINISHED in the given state. */
- @JvmStatic
- fun isBouncerState(state: KeyguardState): Boolean {
- return state == PRIMARY_BOUNCER || state == ALTERNATE_BOUNCER
- }
-
/**
* Whether the device is awake ([PowerInteractor.isAwake]) when we're FINISHED in the given
* keyguard state.
*/
fun deviceIsAwakeInState(state: KeyguardState): Boolean {
+ state.checkValidState()
return when (state) {
OFF -> false
DOZING -> false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index f2821a0..e063380 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -37,6 +37,10 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.AodClockBurnInModel
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.util.ui.value
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
object KeyguardClockViewBinder {
@@ -99,15 +103,20 @@
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
- viewModel.isAodIconsVisible.collect {
- viewModel.currentClock.value?.let {
- if (
- viewModel.isLargeClockVisible.value && it.config.useCustomClockScene
- ) {
- blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
+ combine(
+ viewModel.hasAodIcons,
+ rootViewModel.isNotifIconContainerVisible.map { it.value }
+ ) { hasIcon, isVisible ->
+ hasIcon && isVisible
+ }
+ .distinctUntilChanged()
+ .collect { _ ->
+ viewModel.currentClock.value?.let {
+ if (it.config.useCustomClockScene) {
+ blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
+ }
}
}
- }
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 34a1da5..0637bba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -45,6 +45,7 @@
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
+import com.android.systemui.util.ui.value
import dagger.Lazy
import javax.inject.Inject
@@ -70,6 +71,7 @@
private val rootViewModel: KeyguardRootViewModel,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {}
+
override fun bindData(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) {
return
@@ -121,35 +123,39 @@
private fun getTargetClockFace(clock: ClockController): ClockFaceLayout =
if (keyguardClockViewModel.isLargeClockVisible.value) clock.largeClock.layout
else clock.smallClock.layout
+
private fun getNonTargetClockFace(clock: ClockController): ClockFaceLayout =
if (keyguardClockViewModel.isLargeClockVisible.value) clock.smallClock.layout
else clock.largeClock.layout
fun constrainWeatherClockDateIconsBarrier(constraints: ConstraintSet) {
constraints.apply {
- if (keyguardClockViewModel.isAodIconsVisible.value) {
+ createBarrier(
+ R.id.weather_clock_bc_smartspace_bottom,
+ Barrier.BOTTOM,
+ getDimen(ENHANCED_SMARTSPACE_HEIGHT),
+ (custR.id.weather_clock_time)
+ )
+ if (
+ rootViewModel.isNotifIconContainerVisible.value.value &&
+ keyguardClockViewModel.hasAodIcons.value
+ ) {
createBarrier(
R.id.weather_clock_date_and_icons_barrier_bottom,
Barrier.BOTTOM,
0,
- *intArrayOf(sharedR.id.bc_smartspace_view, R.id.aod_notification_icon_container)
+ *intArrayOf(
+ R.id.aod_notification_icon_container,
+ R.id.weather_clock_bc_smartspace_bottom
+ )
)
} else {
- if (smartspaceViewModel.bcSmartspaceVisibility.value == VISIBLE) {
- createBarrier(
- R.id.weather_clock_date_and_icons_barrier_bottom,
- Barrier.BOTTOM,
- 0,
- (sharedR.id.bc_smartspace_view)
- )
- } else {
- createBarrier(
- R.id.weather_clock_date_and_icons_barrier_bottom,
- Barrier.BOTTOM,
- getDimen(ENHANCED_SMARTSPACE_HEIGHT),
- (R.id.lockscreen_clock_view)
- )
- }
+ createBarrier(
+ R.id.weather_clock_date_and_icons_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ *intArrayOf(R.id.weather_clock_bc_smartspace_bottom)
+ )
}
}
}
@@ -198,6 +204,7 @@
companion object {
private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+
fun getDimen(context: Context, name: String): Int {
val res = context.packageManager.getResourcesForApplication(context.packageName)
val id = res.getIdentifier(name, "dimen", context.packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
index 4128c52..5cf100e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
@@ -34,8 +34,8 @@
alternateBouncerInteractor: AlternateBouncerInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
- val canShowAlternateBouncer: Flow<Boolean> = alternateBouncerInteractor.canShowAlternateBouncer
-
+ private val deviceSupportsAlternateBouncer: Flow<Boolean> =
+ alternateBouncerInteractor.alternateBouncerSupported
private val isTransitioningToOrFromOrShowingAlternateBouncer: Flow<Boolean> =
keyguardTransitionInteractor
.transitionValue(KeyguardState.ALTERNATE_BOUNCER)
@@ -43,8 +43,8 @@
.distinctUntilChanged()
val alternateBouncerWindowRequired: Flow<Boolean> =
- canShowAlternateBouncer.flatMapLatest { canShowAlternateBouncer ->
- if (canShowAlternateBouncer) {
+ deviceSupportsAlternateBouncer.flatMapLatest { deviceSupportsAlternateBouncer ->
+ if (deviceSupportsAlternateBouncer) {
isTransitioningToOrFromOrShowingAlternateBouncer
} else {
flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f5c521a..573b75e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -31,7 +31,6 @@
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
import javax.inject.Inject
@@ -50,7 +49,6 @@
keyguardClockInteractor: KeyguardClockInteractor,
@Application private val applicationScope: CoroutineScope,
aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
- notifsKeyguardInteractor: NotificationsKeyguardInteractor,
@get:VisibleForTesting val shadeInteractor: ShadeInteractor,
private val systemBarUtils: SystemBarUtilsProxy,
configurationInteractor: ConfigurationInteractor,
@@ -90,14 +88,13 @@
currentClock?.let { clock ->
val face = if (isLargeClock) clock.largeClock else clock.smallClock
face.config.hasCustomWeatherDataDisplay
- }
- ?: false
+ } ?: false
}
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay
- ?: false
+ initialValue =
+ currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false
)
val clockShouldBeCentered: StateFlow<Boolean> =
@@ -109,15 +106,14 @@
// To translate elements below smartspace in weather clock to avoid overlapping between date
// element in weather clock and aod icons
- val isAodIconsVisible: StateFlow<Boolean> = combine(aodNotificationIconViewModel.icons.map {
- it.visibleIcons.isNotEmpty()
- }, notifsKeyguardInteractor.areNotificationsFullyHidden) { hasIcons, visible ->
- hasIcons && visible
- }.stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false
- )
+ val hasAodIcons: StateFlow<Boolean> =
+ aodNotificationIconViewModel.icons
+ .map { it.visibleIcons.isNotEmpty() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
val currentClockLayout: StateFlow<ClockLayout> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index d7ac976..0fb29c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -143,7 +143,7 @@
private val isOnLockscreen: Flow<Boolean> =
combine(
- keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) },
+ keyguardTransitionInteractor.isFinishedIn(LOCKSCREEN).onStart { emit(false) },
anyOf(
keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)),
keyguardTransitionInteractor.isInTransition(Edge.create(from = LOCKSCREEN)),
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
index 939dab2..28bdbb2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
@@ -20,13 +20,14 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.log.LogBuffer;
+import com.android.systemui.navigationbar.views.NavigationBar;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Qualifier;
-/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+/** A {@link LogBuffer} for {@link NavigationBar}. */
@Qualifier
@Documented
@Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java
index 46790a6..d09eeb2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java
@@ -19,13 +19,14 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.log.LogBuffer;
+import com.android.systemui.navigationbar.views.NavigationBar;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Qualifier;
-/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+/** A {@link LogBuffer} for {@link NavigationBar}. */
@Qualifier
@Documented
@Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index b393155..13a786a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -573,7 +573,7 @@
}
}
- static @TransitionMode int transitionMode(boolean isTransient, int appearance) {
+ public static @TransitionMode int transitionMode(boolean isTransient, int appearance) {
final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_NAVIGATION_BARS;
if (isTransient) {
return MODE_SEMI_TRANSPARENT;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java
index 4f713d6..abd8bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java
@@ -24,6 +24,7 @@
import androidx.annotation.Nullable;
import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.navigationbar.views.NavigationBar;
import dagger.BindsInstance;
import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index c801662..49fa3ba 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -20,6 +20,8 @@
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
+import com.android.systemui.navigationbar.views.NavigationBar;
+import com.android.systemui.navigationbar.views.NavigationBarView;
/** A controller to handle navigation bars. */
public interface NavigationBarController {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
index 06a78c5..c392c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
@@ -19,6 +19,8 @@
import com.android.internal.statusbar.RegisterStatusBarResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.statusbar.phone.BarTransitions
+import com.android.systemui.navigationbar.views.NavigationBar
+import com.android.systemui.navigationbar.views.NavigationBarView
import javax.inject.Inject
/** A no-op version of [NavigationBarController] for variants like Arc and TV. */
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index f0207aa..1dbdec9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -54,6 +54,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.views.NavigationBar;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index aab4fea..e2ba761 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -24,6 +24,8 @@
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.navigationbar.views.NavigationBarFrame;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.res.R;
import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
index d1ce1f6..1b985d20 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
@@ -32,7 +32,7 @@
import com.android.app.animation.Interpolators;
import com.android.settingslib.Utils;
-import com.android.systemui.navigationbar.buttons.ButtonInterface;
+import com.android.systemui.navigationbar.views.buttons.ButtonInterface;
import com.android.systemui.res.R;
public class NavigationHandle extends View implements ButtonInterface {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 89dce03..e832abb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
@@ -117,11 +117,15 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
+import com.android.systemui.navigationbar.NavBarHelper;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
+import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
-import com.android.systemui.navigationbar.buttons.DeadZone;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.DeadZone;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -1992,7 +1996,7 @@
return mSamplingBounds;
}
- void setNavigationBarLumaSamplingEnabled(boolean enable) {
+ public void setNavigationBarLumaSamplingEnabled(boolean enable) {
if (enable) {
mRegionSamplingHelper.start(mSamplingBounds);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarFrame.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarFrame.java
index 6c531d8..3388f93 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarFrame.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static android.view.MotionEvent.ACTION_OUTSIDE;
@@ -26,7 +26,7 @@
import android.view.MotionEvent;
import android.widget.FrameLayout;
-import com.android.systemui.navigationbar.buttons.DeadZone;
+import com.android.systemui.navigationbar.views.buttons.DeadZone;
public class NavigationBarFrame extends FrameLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
index 2ae0709..96b730c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarInflaterView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
@@ -36,10 +36,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
-import com.android.systemui.navigationbar.buttons.ReverseLinearLayout;
-import com.android.systemui.navigationbar.buttons.ReverseLinearLayout.ReverseRelativeLayout;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout;
+import com.android.systemui.navigationbar.views.buttons.ReverseLinearLayout.ReverseRelativeLayout;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.res.R;
import com.android.systemui.shared.system.QuickStepContract;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarTransitions.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarTransitions.java
index 5442598..251745d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarTransitions.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
@@ -25,7 +25,7 @@
import android.view.View;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index 2c6e297..0f36097 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -66,12 +66,13 @@
import com.android.settingslib.Utils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
-import com.android.systemui.navigationbar.buttons.ContextualButton;
-import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
-import com.android.systemui.navigationbar.buttons.DeadZone;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
+import com.android.systemui.navigationbar.ScreenPinningNotify;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.ContextualButton;
+import com.android.systemui.navigationbar.views.buttons.ContextualButtonGroup;
+import com.android.systemui.navigationbar.views.buttons.DeadZone;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable;
+import com.android.systemui.navigationbar.views.buttons.NearestTouchFrame;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.Recents;
import com.android.systemui.res.R;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonDispatcher.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonDispatcher.java
index fc37b9f..6799a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonDispatcher.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import static com.android.app.animation.Interpolators.LINEAR;
@@ -25,8 +25,6 @@
import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
-import com.android.systemui.navigationbar.NavBarButtonClickLogger;
-
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonInterface.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonInterface.java
index 5f8fafd..c1b4944 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ButtonInterface.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButton.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButton.java
index 517f8e7..5e6bfa8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButton.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButtonGroup.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButtonGroup.java
index 2ace303..c68d25f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ContextualButtonGroup.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import android.annotation.IdRes;
import android.annotation.NonNull;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/DeadZone.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/DeadZone.java
index 8177fde..a6be314 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/DeadZone.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import android.animation.ObjectAnimator;
import android.content.res.Resources;
@@ -27,7 +27,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.res.R;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java
index 686facc..072a4db 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonDrawable.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import android.animation.ArgbEvaluator;
import android.annotation.ColorInt;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
index dbe87ea..1e85d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
@@ -59,7 +59,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.res.R;
import com.android.systemui.shared.navigationbar.KeyButtonRipple;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavBarButtonClickLogger.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavBarButtonClickLogger.kt
index 408acf3..ca58718 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavBarButtonClickLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar
+package com.android.systemui.navigationbar.views.buttons
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavbarOrientationTrackingLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavbarOrientationTrackingLogger.kt
index b1bd286..a5ba17b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NavbarOrientationTrackingLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar
+package com.android.systemui.navigationbar.views.buttons
import android.view.Surface
import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrame.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrame.java
index d780f7c..e1a1a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrame.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import android.content.Context;
import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ReverseLinearLayout.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ReverseLinearLayout.java
index f1e1366..9d50bd6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/ReverseLinearLayout.java
@@ -1,18 +1,20 @@
/*
* Copyright (C) 2016 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import android.annotation.Nullable;
import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index c214361..ff20707 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -74,7 +74,7 @@
@Binds
@PaginatedBaseLayoutType
- fun bindPaginatedBaseGridLayout(impl: PartitionedGridLayout): PaginatableGridLayout
+ fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout
@Binds
@PaginatedBaseLayoutType
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
new file mode 100644
index 0000000..f7c71ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.qs.panels.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.emitOnStart
+import javax.inject.Inject
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class QuickQuickSettingsRowRepository
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ configurationRepository: ConfigurationRepository,
+) {
+ val rows =
+ configurationRepository.onConfigurationChange.emitOnStart().map {
+ resources.getInteger(R.integer.quick_qs_panel_max_rows)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractor.kt
new file mode 100644
index 0000000..5f001df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.QuickQuickSettingsRowRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class QuickQuickSettingsRowInteractor
+@Inject
+constructor(
+ quickQuickSettingsRowRepository: QuickQuickSettingsRowRepository,
+) {
+ val rows = quickQuickSettingsRowRepository.rows
+
+ val defaultRows = 2
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
new file mode 100644
index 0000000..c0371b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.systemui.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
+import com.android.systemui.res.R
+
+@Composable
+fun QuickQuickSettings(
+ viewModel: QuickQuickSettingsViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val sizedTiles by
+ viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList())
+ val tiles = sizedTiles.map { it.tile }
+
+ DisposableEffect(tiles) {
+ val token = Any()
+ tiles.forEach { it.startListening(token) }
+ onDispose { tiles.forEach { it.stopListening(token) } }
+ }
+ val columns by viewModel.columns.collectAsStateWithLifecycle()
+
+ TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
+ items(
+ tiles.size,
+ key = { index -> sizedTiles[index].tile.spec },
+ span = { index -> GridItemSpan(sizedTiles[index].width) }
+ ) { index ->
+ Tile(
+ tile = tiles[index],
+ iconOnly = sizedTiles[index].width == 1,
+ modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
new file mode 100644
index 0000000..b3acace
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class QuickQuickSettingsViewModel
+@Inject
+constructor(
+ tilesInteractor: CurrentTilesInteractor,
+ fixedColumnsSizeViewModel: FixedColumnsSizeViewModel,
+ quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
+ private val iconTilesViewModel: IconTilesViewModel,
+ @Application private val applicationScope: CoroutineScope,
+) {
+
+ val columns = fixedColumnsSizeViewModel.columns
+
+ private val rows =
+ quickQuickSettingsRowInteractor.rows.stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ quickQuickSettingsRowInteractor.defaultRows
+ )
+
+ val tileViewModels: Flow<List<SizedTile<TileViewModel>>> =
+ columns.flatMapLatest { columns ->
+ tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
+ tiles
+ .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+ .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
+ }
+ }
+
+ private val TileSpec.width: Int
+ get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
+
+ companion object {
+ private fun splitInRowsSequence(
+ tiles: List<SizedTile<TileViewModel>>,
+ columns: Int,
+ ): Sequence<List<SizedTile<TileViewModel>>> = sequence {
+ val row = TileRow<TileViewModel>(columns)
+ for (tile in tiles) {
+ check(tile.width <= columns)
+ if (!row.maybeAddTile(tile)) {
+ // Couldn't add tile to previous row, create a row with the current tiles
+ // and start a new one
+ yield(row.tiles)
+ row.clear()
+ row.maybeAddTile(tile)
+ }
+ }
+ if (row.tiles.isNotEmpty()) {
+ yield(row.tiles)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index a04fa38..6ccd169 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -19,6 +19,7 @@
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
import javax.inject.Inject
@@ -29,4 +30,5 @@
val brightnessSliderViewModel: BrightnessSliderViewModel,
val tileGridViewModel: TileGridViewModel,
val editModeViewModel: EditModeViewModel,
+ val quickQuickSettingsViewModel: QuickQuickSettingsViewModel,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index e395d30..66fcbac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -20,42 +20,54 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
-import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the Quick Settings Shade scene. */
@SysUISingleton
class QuickSettingsShadeSceneViewModel
@Inject
constructor(
- shadeInteractor: ShadeInteractor,
+ private val shadeInteractor: ShadeInteractor,
val overlayShadeViewModel: OverlayShadeViewModel,
- val brightnessSliderViewModel: BrightnessSliderViewModel,
- val tileGridViewModel: TileGridViewModel,
- val editModeViewModel: EditModeViewModel,
- val qsSceneAdapter: QSSceneAdapter,
+ val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
+ @Application applicationScope: CoroutineScope,
) {
+
+ val isEditing: StateFlow<Boolean> = quickSettingsContainerViewModel.editModeViewModel.isEditing
+
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- MutableStateFlow(
- mapOf(
- if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
- Swipe.Up
- } else {
- Swipe.Down
- } to SceneFamilies.Home,
- Back to SceneFamilies.Home,
- )
+ isEditing
+ .map { editing -> destinations(editing) }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ destinations(isEditing.value)
)
- .asStateFlow()
+
+ private fun destinations(editing: Boolean): Map<UserAction, UserActionResult> {
+ return buildMap {
+ put(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ },
+ UserActionResult(SceneFamilies.Home)
+ )
+ if (!editing) {
+ put(Back, UserActionResult(SceneFamilies.Home))
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4b82e0f..e07b057 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -91,11 +91,11 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationBar;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.NavigationBar;
+import com.android.systemui.navigationbar.views.NavigationBarView;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 2b717cb..8d0a386 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -57,8 +57,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.QuickStepContract;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 59c1592..ba4c29a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -47,8 +47,6 @@
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -150,8 +148,6 @@
/**
* True if either the primary or alternate bouncer are open, meaning the hub should not receive
* any touch input.
- *
- * Tracks [KeyguardTransitionInteractor.isFinishedInState] for [KeyguardState.isBouncerState].
*/
private var anyBouncerShowing = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6b4e44f..04de2c2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -158,8 +158,8 @@
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 89b7ccf..e505ef7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -203,7 +203,9 @@
@Override
public void start() {
mJavaAdapter.alwaysCollectFlow(
- mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE),
+ mKeyguardTransitionInteractorLazy.get().isFinishedIn(
+ /* scene */ Scenes.Gone,
+ /* stateWithoutSceneContainer */ GONE),
(Boolean isFinishedInState) -> {
if (isFinishedInState) {
setLeaveOpenOnKeyguardHide(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index e9306a5..069ae93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -41,4 +41,7 @@
val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>>
fun setHeadsUpAnimatingAway(animatingAway: Boolean)
+
+ /** Snooze the currently pinned HUN. */
+ fun snooze()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 4a6553f..cdab108 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -101,10 +101,17 @@
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
+
fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
+
fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
headsUpRepository.setHeadsUpAnimatingAway(animatingAway)
}
+
+ /** Snooze the currently pinned HUN. */
+ fun snooze() {
+ headsUpRepository.snooze()
+ }
}
class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 48c89f8..5e91786 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -32,6 +32,7 @@
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.database.ContentObserver
import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Bundle
import android.os.Handler
import android.os.PowerManager
import android.os.SystemProperties
@@ -368,6 +369,11 @@
PendingIntent.FLAG_IMMUTABLE
)
+ // Replace "System UI" app name with "Android System"
+ val bundle = Bundle()
+ bundle.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ context.getString(com.android.internal.R.string.android_system_label))
+
val builder =
Notification.Builder(context, NotificationChannels.ALERTS)
.setTicker(titleStr)
@@ -375,9 +381,11 @@
.setContentText(textStr)
.setSmallIcon(com.android.systemui.res.R.drawable.ic_settings)
.setCategory(Notification.CATEGORY_SYSTEM)
+ .setTimeoutAfter(/* one day in ms */ 24 * 60 * 60 * 1000L)
.setAutoCancel(true)
.addAction(android.R.drawable.button_onoff_indicator_off, actionStr, pendingIntent)
.setContentIntent(pendingIntent)
+ .addExtras(bundle)
notificationManager.notify(SystemMessage.NOTE_ADAPTIVE_NOTIFICATIONS, builder.build())
hasSeenEdu = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 4a043d3..9e943ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -104,6 +104,7 @@
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
+import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -1584,6 +1585,9 @@
/* headerView= */ headerView,
/* onClickListener= */ mExpandClickListener
);
+ if (TransparentHeaderFix.isEnabled()) {
+ updateBackgroundForGroupState();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt
new file mode 100644
index 0000000..0b01510
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification transparent header fix flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object TransparentHeaderFix {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the flag enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationTransparentHeaderFix()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 715c6e6..cb03334 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3499,6 +3499,13 @@
setIsBeingDragged(true);
}
+ // Only when scene container is enabled, mark that we are being dragged so that we start
+ // dispatching the rest of the gesture to scene container.
+ void startDraggingOnHun() {
+ SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ setIsBeingDragged(true);
+ }
+
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (!isScrollingEnabled()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 12f8f69..58b72e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -59,6 +59,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.view.OneShotPreDrawListener;
import com.android.systemui.Dumpable;
import com.android.systemui.ExpandHelper;
@@ -126,6 +127,7 @@
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -168,6 +170,7 @@
private final NotificationVisibilityProvider mVisibilityProvider;
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
private final HeadsUpManager mHeadsUpManager;
+ private HeadsUpTouchHelper mHeadsUpTouchHelper;
private final NotificationRoundnessManager mNotificationRoundnessManager;
private final TunerService mTunerService;
private final DeviceProvisionedController mDeviceProvisionedController;
@@ -706,6 +709,7 @@
NotificationVisibilityProvider visibilityProvider,
NotificationWakeUpCoordinator wakeUpCoordinator,
HeadsUpManager headsUpManager,
+ Provider<IStatusBarService> statusBarService,
NotificationRoundnessManager notificationRoundnessManager,
TunerService tunerService,
DeviceProvisionedController deviceProvisionedController,
@@ -758,6 +762,14 @@
mVisibilityProvider = visibilityProvider;
mWakeUpCoordinator = wakeUpCoordinator;
mHeadsUpManager = headsUpManager;
+ if (SceneContainerFlag.isEnabled()) {
+ mHeadsUpTouchHelper = new HeadsUpTouchHelper(
+ mHeadsUpManager,
+ statusBarService.get(),
+ getHeadsUpCallback(),
+ new HeadsUpNotificationViewControllerEmptyImpl()
+ );
+ }
mNotificationRoundnessManager = notificationRoundnessManager;
mTunerService = tunerService;
mDeviceProvisionedController = deviceProvisionedController;
@@ -2000,6 +2012,13 @@
&& !mView.isExpandingNotification()) {
scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
}
+ boolean hunWantsIt = false;
+ if (shouldHeadsUpHandleTouch()) {
+ hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev);
+ if (hunWantsIt) {
+ mView.startDraggingOnHun();
+ }
+ }
boolean swipeWantsIt = false;
if (mLongPressedView == null && !mView.isBeingDragged()
&& !mView.isExpandingNotification()
@@ -2029,7 +2048,7 @@
&& ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
}
- return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt;
+ return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt || hunWantsIt;
}
@Override
@@ -2081,6 +2100,10 @@
&& !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
scrollerWantsIt = mView.onScrollTouch(ev);
}
+ boolean hunWantsIt = false;
+ if (shouldHeadsUpHandleTouch()) {
+ hunWantsIt = mHeadsUpTouchHelper.onTouchEvent(ev);
+ }
// Check if we need to clear any snooze leavebehinds
if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
@@ -2101,7 +2124,8 @@
mView.setCheckForLeaveBehind(true);
}
traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
- return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt;
+ return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt
+ || hunWantsIt;
}
private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {
@@ -2128,6 +2152,11 @@
break;
}
}
+
+ private boolean shouldHeadsUpHandleTouch() {
+ return SceneContainerFlag.isEnabled() && mLongPressedView == null
+ && !mSwipeHelper.isSwiping();
+ }
}
private class NotifStackControllerImpl implements NotifStackController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 08e81d5..eb1f778 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
@@ -43,6 +44,7 @@
private val interactor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
private val shadeSceneViewModel: ShadeSceneViewModel,
+ private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
featureFlags: FeatureFlagsClassic,
) : FlowDumperImpl(dumpManager) {
/** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
@@ -74,6 +76,10 @@
/** Whether or not the notification scrim should be clickable. */
val isClickable: StateFlow<Boolean> = shadeSceneViewModel.isClickable
+ /** True when a HUN is pinned or animating away. */
+ val isHeadsUpOrAnimatingAway: Flow<Boolean> =
+ headsUpNotificationInteractor.isHeadsUpOrAnimatingAway
+
/** Corner rounding of the stack */
val shadeScrimRounding: Flow<ShadeScrimRounding> =
interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding")
@@ -103,6 +109,11 @@
fun setScrolledToTop(scrolledToTop: Boolean) {
interactor.setScrolledToTop(scrolledToTop)
}
+
+ /** Snooze the currently pinned HUN. */
+ fun snoozeHun() {
+ headsUpNotificationInteractor.snooze()
+ }
}
// Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 8e58ffb..1c57346 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -287,7 +287,7 @@
/** Are we on the dream without the shade/qs? */
private val isDreamingWithoutShade: Flow<Boolean> =
combine(
- keyguardTransitionInteractor.isFinishedInState(DREAMING),
+ keyguardTransitionInteractor.isFinishedIn(DREAMING),
isAnyExpanded,
) { isDreaming, isAnyExpanded ->
isDreaming && !isAnyExpanded
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 0a02381..a0d4ca2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -41,7 +41,7 @@
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.RemoteAnimationRunnerCompat;
import com.android.systemui.display.data.repository.DisplayMetricsRepository;
-import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 3bc8e27..88d3e07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -20,7 +20,7 @@
import androidx.lifecycle.LifecycleRegistry
import com.android.keyguard.AuthKeyguardMessageArea
import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.navigationbar.NavigationBarView
+import com.android.systemui.navigationbar.views.NavigationBarView
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.qs.QSPanelController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 491db30..462ae7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -146,7 +146,7 @@
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt
new file mode 100644
index 0000000..9f76429
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController
+
+/** Empty impl of [HeadsUpNotificationViewController] for use with Scene Container */
+class HeadsUpNotificationViewControllerEmptyImpl : HeadsUpNotificationViewController {
+ override fun setHeadsUpDraggingStartingHeight(startHeight: Int) {}
+
+ override fun setTrackedHeadsUp(expandableNotificationRow: ExpandableNotificationRow?) {}
+
+ override fun startExpand(
+ newX: Float,
+ newY: Float,
+ startTracking: Boolean,
+ expandedHeight: Float
+ ) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 198272e..7f16e18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,6 +23,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Gefingerpoken;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -122,9 +123,14 @@
+ mPickedChild.getTranslationY());
mPanel.setHeadsUpDraggingStartingHeight(startHeight);
mPanel.startExpand(x, y, true /* startTracking */, startHeight);
- // This call needs to be after the expansion start otherwise we will get a
- // flicker of one frame as it's not expanded yet.
- mHeadsUpManager.unpinAll(true);
+
+ // TODO(b/340514839): Figure out where to move this side effect in flexiglass
+ if (!SceneContainerFlag.isEnabled()) {
+ // This call needs to be after the expansion start otherwise we will get a
+ // flicker of one frame as it's not expanded yet.
+ mHeadsUpManager.unpinAll(true);
+ }
+
clearNotificationEffects();
endMotion();
return true;
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 b40bf56..0316b0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -77,9 +77,9 @@
import com.android.systemui.keyguard.shared.model.KeyguardDone;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
+import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 9449659..62bd8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -349,6 +349,7 @@
false
}
}
+ .flowOn(bgDispatcher)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val carrierId =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 3e01180..4869114 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -20,7 +20,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.policy.BatteryController
@@ -48,6 +49,7 @@
constructor(
@Application scope: CoroutineScope,
headsUpNotificationInteractor: HeadsUpNotificationInteractor,
+ sceneInteractor: SceneInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardStatusBarInteractor: KeyguardStatusBarInteractor,
batteryController: BatteryController,
@@ -55,11 +57,11 @@
/** True if this view should be visible and false otherwise. */
val isVisible: StateFlow<Boolean> =
combine(
+ sceneInteractor.currentScene,
keyguardInteractor.isDozing,
- keyguardInteractor.statusBarState,
headsUpNotificationInteractor.showHeadsUpStatusBar,
- ) { isDozing, statusBarState, showHeadsUpStatusBar ->
- !isDozing && statusBarState == StatusBarState.KEYGUARD && !showHeadsUpStatusBar
+ ) { currentScene, isDozing, showHeadsUpStatusBar ->
+ currentScene == Scenes.Lockscreen && !isDozing && !showHeadsUpStatusBar
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 4963aae..c7fc445 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -202,8 +202,8 @@
}
boolean currentUser = userId == mUserTracker.getUserId();
boolean isAsleep = themeOverlayControllerWakefulnessDeprecation()
- ? mKeyguardTransitionInteractor.isFinishedInStateWhereValue(
- KeyguardState.Companion::deviceIsAsleepInState)
+ ? KeyguardState.Companion.deviceIsAsleepInState(
+ mKeyguardTransitionInteractor.getFinishedState())
: mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP;
if (currentUser && !mAcceptColorEvents && isAsleep) {
@@ -525,7 +525,7 @@
if (themeOverlayControllerWakefulnessDeprecation()) {
mJavaAdapter.alwaysCollectFlow(
- mKeyguardTransitionInteractor.isFinishedInState(KeyguardState.DOZING),
+ mKeyguardTransitionInteractor.isFinishedIn(KeyguardState.DOZING),
isFinishedInDozing -> {
if (isFinishedInDozing) whenAsleepHandler.run();
});
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index ec6f862..fb40235 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -14,18 +14,18 @@
package com.android.systemui.tuner;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_CODE_END;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_CODE_START;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_IMAGE_DELIM;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.MENU_IME_ROTATE;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAVSPACE;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_LEFT;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_RIGHT;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_VIEWS;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractButton;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractImage;
-import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractKeycode;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.KEY;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.KEY_CODE_END;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.KEY_CODE_START;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.KEY_IMAGE_DELIM;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.MENU_IME_ROTATE;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.NAVSPACE;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.NAV_BAR_LEFT;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.NAV_BAR_RIGHT;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.NAV_BAR_VIEWS;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.extractButton;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.extractImage;
+import static com.android.systemui.navigationbar.views.NavigationBarInflaterView.extractKeycode;
import android.annotation.Nullable;
import android.app.AlertDialog;
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 8c46f9a..28ac2c0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -106,3 +106,13 @@
): Flow<R> {
return combine(flow1, flow2, flow3, trifunction)
}
+
+fun <T1, T2, T3, T4, R> combineFlows(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ transform: (T1, T2, T3, T4) -> R
+): Flow<R> {
+ return combine(flow, flow2, flow3, flow4, transform)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index aee441a..c45f98e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -567,7 +567,7 @@
streamStateW(stream).levelMax = Math.max(1, getAudioManagerStreamMaxVolume(stream));
updateStreamMuteW(stream, mAudio.isStreamMute(stream));
final StreamState ss = streamStateW(stream);
- ss.muteSupported = mAudio.isStreamAffectedByMute(stream);
+ ss.muteSupported = mAudio.isStreamMutableByUi(stream);
ss.name = STREAMS.get(stream);
checkRoutedToBluetoothW(stream);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 64c162f..639b53b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -46,6 +46,7 @@
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleCoroutineScope;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
@@ -159,7 +160,9 @@
ArgumentCaptor<LifecycleObserver> observerCaptor =
ArgumentCaptor.forClass(LifecycleObserver.class);
verify(mLifecycleRegistry, atLeast(1)).addObserver(observerCaptor.capture());
- mLifecycleObservers.addAll(observerCaptor.getAllValues());
+ mLifecycleObservers.addAll(observerCaptor.getAllValues().stream().filter(
+ lifecycleObserver -> !(lifecycleObserver instanceof LifecycleCoroutineScope))
+ .toList());
updateLifecycle(Lifecycle.State.RESUMED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 1c327af..f34058b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -20,6 +20,7 @@
import android.os.Looper
import android.testing.TestableLooper
import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.TYPE_EXTERNAL
import android.view.Display.TYPE_INTERNAL
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -27,7 +28,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -45,6 +45,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.kotlin.eq
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -63,8 +64,8 @@
@Before
fun setup() {
- setDisplays(emptyList())
- setAllDisplaysIncludingDisabled()
+ setDisplays(DEFAULT_DISPLAY)
+ setAllDisplaysIncludingDisabled(DEFAULT_DISPLAY)
displayRepository =
DisplayRepositoryImpl(
displayManager,
@@ -81,7 +82,7 @@
testScope.runTest {
val value by latestDisplayFlowValue()
- assertThat(value).isEmpty()
+ assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
verify(displayManager).registerDisplayListener(any(), eq(testHandler), anyLong())
}
@@ -91,7 +92,7 @@
testScope.runTest {
val value by latestDisplayFlowValue()
- assertThat(value).isEmpty()
+ assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
verify(displayManager).registerDisplayListener(any(), eq(testHandler), anyLong())
}
@@ -103,14 +104,14 @@
testScope.runTest {
val firstSubscriber by latestDisplayFlowValue()
- assertThat(firstSubscriber).isEmpty()
+ assertThat(firstSubscriber).hasSize(1) // Default display only
verify(displayManager, times(1))
.registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
val innerScope = TestScope()
innerScope.runTest {
val secondSubscriber by latestDisplayFlowValue()
- assertThat(secondSubscriber).isEmpty()
+ assertThat(secondSubscriber).hasSize(1)
// No new registration, just the precedent one.
verify(displayManager, times(1))
@@ -120,7 +121,7 @@
// Let's make sure it has *NOT* been unregistered, as there is still a subscriber.
setDisplays(1)
sendOnDisplayAdded(1)
- assertThat(firstSubscriber?.ids()).containsExactly(1)
+ assertThat(firstSubscriber?.ids()).contains(1)
}
// All subscribers are done, unregister should have been called.
@@ -135,7 +136,7 @@
setDisplays(1)
sendOnDisplayAdded(1)
- assertThat(value?.ids()).containsExactly(1)
+ assertThat(value?.ids()).contains(1)
}
@Test
@@ -152,7 +153,7 @@
setDisplays(1, 2, 3)
sendOnDisplayRemoved(4)
- assertThat(value?.ids()).containsExactly(1, 2, 3)
+ assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY, 1, 2, 3)
}
@Test
@@ -168,7 +169,7 @@
displayListener.value.onDisplayChanged(4)
- assertThat(value?.ids()).containsExactly(1, 2, 3, 4)
+ assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY, 1, 2, 3, 4)
}
@Test
@@ -435,37 +436,29 @@
val defaultDisplayOff by latestDefaultDisplayOffFlowValue()
setDisplays(
listOf(
- display(
- type = TYPE_INTERNAL,
- id = Display.DEFAULT_DISPLAY,
- state = Display.STATE_OFF
- )
+ display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_OFF)
)
)
- displayListener.value.onDisplayChanged(Display.DEFAULT_DISPLAY)
+ displayListener.value.onDisplayChanged(DEFAULT_DISPLAY)
assertThat(defaultDisplayOff).isTrue()
setDisplays(
listOf(
- display(
- type = TYPE_INTERNAL,
- id = Display.DEFAULT_DISPLAY,
- state = Display.STATE_ON
- )
+ display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_ON)
)
)
- displayListener.value.onDisplayChanged(Display.DEFAULT_DISPLAY)
+ displayListener.value.onDisplayChanged(DEFAULT_DISPLAY)
assertThat(defaultDisplayOff).isFalse()
}
@Test
fun displayFlow_startsWithDefaultDisplayBeforeAnyEvent() =
testScope.runTest {
- setDisplays(Display.DEFAULT_DISPLAY)
+ setDisplays(DEFAULT_DISPLAY)
val value by latestDisplayFlowValue()
- assertThat(value?.ids()).containsExactly(Display.DEFAULT_DISPLAY)
+ assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
}
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
@@ -565,6 +558,8 @@
}
private fun setDisplays(vararg ids: Int) {
- setDisplays(ids.map { display(type = TYPE_EXTERNAL, id = it) })
+ // DEFAULT_DISPLAY always there
+ val idsToSet = ids.toSet() + DEFAULT_DISPLAY
+ setDisplays(idsToSet.map { display(type = TYPE_EXTERNAL, id = it) })
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
index 6398a5a..c1bd378 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
@@ -19,26 +19,22 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
-import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.whenever
+import org.junit.runners.JUnit4
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@@ -54,35 +50,13 @@
fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() =
testScope.runTest {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
- val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer)
val alternateBouncerWindowRequired by
collectLastValue(underTest.alternateBouncerWindowRequired)
- givenCanShowAlternateBouncer()
fingerprintPropertyRepository.supportsUdfps()
transitionRepository.sendTransitionSteps(
listOf(
- stepToLockscreen(0f, TransitionState.STARTED),
- stepToLockscreen(.4f),
- stepToLockscreen(1f, TransitionState.FINISHED),
- ),
- testScope,
- )
- assertThat(canShowAlternateBouncer).isTrue()
- transitionRepository.sendTransitionSteps(
- listOf(
- stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
- stepFromLockscreenToAlternateBouncer(.4f),
- stepFromLockscreenToAlternateBouncer(.6f),
- ),
- testScope,
- )
- assertThat(canShowAlternateBouncer).isTrue()
- assertThat(alternateBouncerWindowRequired).isTrue()
-
- transitionRepository.sendTransitionSteps(
- listOf(
stepFromAlternateBouncer(0f, TransitionState.STARTED),
- stepFromAlternateBouncer(.2f),
+ stepFromAlternateBouncer(.4f),
stepFromAlternateBouncer(.6f),
),
testScope,
@@ -104,21 +78,13 @@
mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
val alternateBouncerWindowRequired by
collectLastValue(underTest.alternateBouncerWindowRequired)
- givenCanShowAlternateBouncer()
fingerprintPropertyRepository.supportsUdfps()
transitionRepository.sendTransitionSteps(
listOf(
- stepToLockscreen(0f, TransitionState.STARTED),
- stepToLockscreen(.4f),
- stepToLockscreen(1f, TransitionState.FINISHED),
- ),
- testScope,
- )
- transitionRepository.sendTransitionSteps(
- listOf(
- stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
- stepFromLockscreenToAlternateBouncer(.4f),
- stepFromLockscreenToAlternateBouncer(.6f),
+ stepFromAlternateBouncer(0f, TransitionState.STARTED),
+ stepFromAlternateBouncer(.4f),
+ stepFromAlternateBouncer(.6f),
+ stepFromAlternateBouncer(1f),
),
testScope,
)
@@ -131,23 +97,13 @@
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
val alternateBouncerWindowRequired by
collectLastValue(underTest.alternateBouncerWindowRequired)
- givenCanShowAlternateBouncer()
fingerprintPropertyRepository.supportsUdfps()
transitionRepository.sendTransitionSteps(
listOf(
- stepFromLockscreenToDozing(0f, TransitionState.STARTED),
- stepFromLockscreenToDozing(.4f),
- stepFromLockscreenToDozing(.6f),
- stepFromLockscreenToDozing(1f, TransitionState.FINISHED),
- ),
- testScope,
- )
- assertThat(alternateBouncerWindowRequired).isFalse()
- transitionRepository.sendTransitionSteps(
- listOf(
stepFromDozingToLockscreen(0f, TransitionState.STARTED),
stepFromDozingToLockscreen(.4f),
stepFromDozingToLockscreen(.6f),
+ stepFromDozingToLockscreen(1f),
),
testScope,
)
@@ -160,39 +116,19 @@
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
val alternateBouncerWindowRequired by
collectLastValue(underTest.alternateBouncerWindowRequired)
- givenCanShowAlternateBouncer()
fingerprintPropertyRepository.supportsRearFps()
transitionRepository.sendTransitionSteps(
listOf(
- stepToLockscreen(0f, TransitionState.STARTED),
- stepToLockscreen(.4f),
- stepToLockscreen(1f, TransitionState.FINISHED),
- ),
- testScope,
- )
- transitionRepository.sendTransitionSteps(
- listOf(
- stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
- stepFromLockscreenToAlternateBouncer(.4f),
- stepFromLockscreenToAlternateBouncer(.6f),
+ stepFromAlternateBouncer(0f, TransitionState.STARTED),
+ stepFromAlternateBouncer(.4f),
+ stepFromAlternateBouncer(.6f),
+ stepFromAlternateBouncer(1f),
),
testScope,
)
assertThat(alternateBouncerWindowRequired).isFalse()
}
- private fun stepToLockscreen(
- value: Float,
- state: TransitionState = TransitionState.RUNNING
- ): TransitionStep {
- return step(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- value = value,
- transitionState = state,
- )
- }
-
private fun stepFromAlternateBouncer(
value: Float,
state: TransitionState = TransitionState.RUNNING
@@ -205,18 +141,6 @@
)
}
- private fun stepFromLockscreenToAlternateBouncer(
- value: Float,
- state: TransitionState = TransitionState.RUNNING
- ): TransitionStep {
- return step(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = value,
- transitionState = state,
- )
- }
-
private fun stepFromDozingToLockscreen(
value: Float,
state: TransitionState = TransitionState.RUNNING
@@ -229,18 +153,6 @@
)
}
- private fun stepFromLockscreenToDozing(
- value: Float,
- state: TransitionState = TransitionState.RUNNING
- ): TransitionStep {
- return step(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DOZING,
- value = value,
- transitionState = state,
- )
- }
-
private fun step(
from: KeyguardState,
to: KeyguardState,
@@ -255,16 +167,4 @@
ownerName = "AlternateBouncerViewModelTest"
)
}
-
- /**
- * Given the alternate bouncer parameters are set so that the alternate bouncer can show, aside
- * from the fingerprint modality.
- */
- private fun givenCanShowAlternateBouncer() {
- kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
- whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
- whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index df8eafe..196bbb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.views.NavigationBar;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shared.recents.utilities.Utilities;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
index 85244fd..00e79f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -38,6 +38,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
index a358c18..e58c8f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doNothing;
@@ -32,7 +32,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
import com.android.systemui.recents.OverviewProxyService;
import org.junit.After;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 982a269..98ff6c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
@@ -28,7 +28,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
-import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
+import static com.android.systemui.navigationbar.views.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.google.common.truth.Truth.assertThat;
@@ -39,7 +39,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -86,10 +86,15 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
-import com.android.systemui.navigationbar.buttons.DeadZone;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.NavBarHelper;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.views.buttons.DeadZone;
+import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
+import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
index c7a92d2..3621ab9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.navigationbar.views;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -32,6 +32,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
index 841f620..403a883 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.ACTION_UP;
@@ -26,12 +26,12 @@
import static android.view.KeyEvent.KEYCODE_HOME;
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
-import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
index 7eb7c8e..79c4a96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
index 6a3c615..7941a68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.navigationbar.views.buttons;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index ce2491b..1060b62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -53,6 +53,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
@@ -99,6 +100,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -135,6 +137,8 @@
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private HeadsUpTouchHelper.Callback mHeadsUpCallback;
+ @Mock private Provider<IStatusBarService> mStatusBarService;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@Mock private TunerService mTunerService;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -973,6 +977,8 @@
when(mNotificationStackScrollLayout.getViewTreeObserver())
.thenReturn(viewTreeObserver);
when(mNotificationStackScrollLayout.getContext()).thenReturn(getContext());
+ when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
+ when(mHeadsUpCallback.getContext()).thenReturn(getContext());
mController = new NotificationStackScrollLayoutController(
mNotificationStackScrollLayout,
true,
@@ -981,6 +987,7 @@
mVisibilityProvider,
mNotificationWakeUpCoordinator,
mHeadsUpManager,
+ mStatusBarService,
mNotificationRoundnessManager,
mTunerService,
mDeviceProvisionedController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 53e643e..0cb28cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -52,28 +52,17 @@
import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.TestScopeProvider;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeViewStateProvider;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository;
-import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.phone.ui.TintedIconManager;
@@ -82,14 +71,11 @@
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.ui.viewmodel.KeyguardStatusBarViewModel;
import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -150,11 +136,7 @@
private KeyguardStatusBarViewController mController;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
- private final TestScope mTestScope = TestScopeProvider.getTestScope();
- private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private KeyguardInteractor mKeyguardInteractor;
- private KeyguardStatusBarViewModel mViewModel;
@Before
public void setup() throws Exception {
@@ -163,28 +145,6 @@
MockitoAnnotations.initMocks(this);
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
- KeyguardTransitionInteractor keyguardTransitionInteractor =
- mKosmos.getKeyguardTransitionInteractor();
- mKeyguardInteractor = new KeyguardInteractor(
- mKeyguardRepository,
- mCommandQueue,
- PowerInteractorFactory.create().getPowerInteractor(),
- new FakeKeyguardBouncerRepository(),
- new ConfigurationInteractor(new FakeConfigurationRepository()),
- new FakeShadeRepository(),
- keyguardTransitionInteractor,
- () -> mKosmos.getSceneInteractor(),
- () -> mKosmos.getFromGoneTransitionInteractor(),
- () -> mKosmos.getFromLockscreenTransitionInteractor(),
- () -> mKosmos.getSharedNotificationContainerInteractor(),
- mTestScope);
- mViewModel =
- new KeyguardStatusBarViewModel(
- mTestScope.getBackgroundScope(),
- mKosmos.getHeadsUpNotificationInteractor(),
- mKeyguardInteractor,
- new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()),
- mBatteryController);
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {
@@ -212,7 +172,7 @@
mKeyguardStateController,
mKeyguardBypassController,
mKeyguardUpdateMonitor,
- mViewModel,
+ mKosmos.getKeyguardStatusBarViewModel(),
mBiometricUnlockController,
mStatusBarStateController,
mStatusBarContentInsetsProvider,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index d88289d..ef4e734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -26,8 +26,10 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
@@ -85,6 +87,7 @@
KeyguardStatusBarViewModel(
testScope.backgroundScope,
headsUpNotificationInteractor,
+ kosmos.sceneInteractor,
keyguardInteractor,
keyguardStatusBarInteractor,
batteryController,
@@ -95,7 +98,7 @@
fun isVisible_dozing_false() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
keyguardRepository.setIsDozing(true)
@@ -103,21 +106,21 @@
}
@Test
- fun isVisible_statusBarStateShade_false() =
+ fun isVisible_sceneShade_false() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
assertThat(latest).isFalse()
}
@Test
- fun isVisible_statusBarStateShadeLocked_false() =
+ fun isVisible_sceneBouncer_false() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
- keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
assertThat(latest).isFalse()
}
@@ -130,7 +133,7 @@
// WHEN HUN displayed on the bypass lock screen
headsUpRepository.setNotifications(FakeHeadsUpRowRepository("key 0", isPinned = true))
keyguardTransitionRepository.emitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
faceAuthRepository.isBypassEnabled.value = true
// THEN KeyguardStatusBar is NOT visible to make space for HeadsUpStatusBar
@@ -138,11 +141,11 @@
}
@Test
- fun isVisible_statusBarStateKeyguard_andNotDozing_andNotShowingHeadsUpStatusBar_true() =
+ fun isVisible_sceneLockscreen_andNotDozing_andNotShowingHeadsUpStatusBar_true() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
keyguardRepository.setIsDozing(false)
assertThat(latest).isTrue()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 5db9d31..9dae44d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui
import android.app.ActivityManager
-import android.app.DreamManager
import android.app.admin.DevicePolicyManager
import android.app.trust.TrustManager
import android.os.UserManager
@@ -33,7 +32,6 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -95,7 +93,6 @@
@get:Provides val demoModeController: DemoModeController = mock(),
@get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
@get:Provides val dozeParameters: DozeParameters = mock(),
- @get:Provides val dreamManager: DreamManager = mock(),
@get:Provides val dumpManager: DumpManager = mock(),
@get:Provides val headsUpManager: HeadsUpManager = mock(),
@get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(),
@@ -133,7 +130,6 @@
@get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
@get:Provides val communalInteractor: CommunalInteractor = mock(),
- @get:Provides val communalSceneInteractor: CommunalSceneInteractor = mock(),
@get:Provides val sceneLogger: SceneLogger = mock(),
@get:Provides val trustManager: TrustManager = mock(),
@get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index 744b127..edf77a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -16,9 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
-import android.service.dream.dreamManager
import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -37,10 +35,8 @@
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
communalInteractor = communalInteractor,
- communalSceneInteractor = communalSceneInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
- dreamManager = dreamManager
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index 3c62b44..b5e6f75 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -23,7 +23,6 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.ui.systemBarUtilsProxy
val Kosmos.keyguardClockViewModel by
@@ -32,7 +31,6 @@
keyguardClockInteractor = keyguardClockInteractor,
applicationScope = applicationCoroutineScope,
aodNotificationIconViewModel = notificationIconContainerAlwaysOnDisplayViewModel,
- notifsKeyguardInteractor = notificationsKeyguardInteractor,
shadeInteractor = shadeInteractor,
systemBarUtils = systemBarUtilsProxy,
configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 10ff731..0666820 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -68,6 +68,7 @@
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiInteractor
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
import com.android.systemui.util.time.systemClock
import com.android.systemui.volume.domain.interactor.volumeDialogInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -102,6 +103,7 @@
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+ val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel }
val powerRepository by lazy { kosmos.fakePowerRepository }
val clock by lazy { kosmos.systemClock }
val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryKosmos.kt
new file mode 100644
index 0000000..da55e21
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.systemui.qs.panels.data.repository
+
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+
+val Kosmos.quickQuickSettingsRowRepository by
+ Kosmos.Fixture {
+ testCase.context.orCreateTestableResources
+ QuickQuickSettingsRowRepository(testCase.context.resources, configurationRepository)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractorKosmos.kt
new file mode 100644
index 0000000..3fcbc8d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QuickQuickSettingsRowInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.quickQuickSettingsRowRepository
+
+val Kosmos.quickQuickSettingsRowInteractor by
+ Kosmos.Fixture { QuickQuickSettingsRowInteractor(quickQuickSettingsRowRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
new file mode 100644
index 0000000..40d2624
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.panels.domain.interactor.quickQuickSettingsRowInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.quickQuickSettingsViewModel by
+ Kosmos.Fixture {
+ QuickQuickSettingsViewModel(
+ currentTilesInteractor,
+ fixedColumnsSizeViewModel,
+ quickQuickSettingsRowInteractor,
+ iconTilesViewModel,
+ applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
new file mode 100644
index 0000000..779634d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.systemui.qs.ui.viewmodel
+
+import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
+
+val Kosmos.quickSettingsContainerViewModel by
+ Kosmos.Fixture {
+ QuickSettingsContainerViewModel(
+ brightnessSliderViewModel,
+ tileGridViewModel,
+ editModeViewModel,
+ quickQuickSettingsViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
similarity index 62%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index 8adb26f..c56c56c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -14,24 +14,19 @@
* limitations under the License.
*/
-package com.android.systemui.shade.ui.viewmodel
+package com.android.systemui.qs.ui.viewmodel
-import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
-import com.android.systemui.qs.ui.adapter.qsSceneAdapter
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
Kosmos.Fixture {
QuickSettingsShadeSceneViewModel(
- overlayShadeViewModel = overlayShadeViewModel,
- brightnessSliderViewModel = brightnessSliderViewModel,
- tileGridViewModel = tileGridViewModel,
- editModeViewModel = editModeViewModel,
- qsSceneAdapter = qsSceneAdapter,
shadeInteractor = shadeInteractor,
+ overlayShadeViewModel = overlayShadeViewModel,
+ quickSettingsContainerViewModel = quickSettingsContainerViewModel,
+ applicationScope = applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
index 641a757..2f17ca8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
@@ -18,8 +18,11 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,6 +33,25 @@
private val mutableTransitionState =
MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(Scenes.Lockscreen))
+suspend fun Kosmos.setTransition(
+ sceneTransition: ObservableTransitionState,
+ stateTransition: TransitionStep? = null,
+ scope: TestScope = testScope,
+ repository: SceneContainerRepository = sceneContainerRepository
+) {
+ if (SceneContainerFlag.isEnabled) {
+ setSceneTransition(sceneTransition, scope, repository)
+ } else {
+ if (stateTransition == null) throw IllegalArgumentException("No transitionStep provided")
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = stateTransition.from,
+ to = stateTransition.to,
+ testScope = scope,
+ throughTransitionState = stateTransition.transitionState
+ )
+ }
+}
+
fun Kosmos.setSceneTransition(
transition: ObservableTransitionState,
scope: TestScope = testScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index 7bf77e5..492e87b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -30,7 +30,12 @@
override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
MutableStateFlow(emptySet())
+
override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
isHeadsUpAnimatingAway.value = animatingAway
}
+
+ override fun snooze() {
+ // do nothing
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index a0e9303..afb8acb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationsPlaceholderViewModel by Fixture {
@@ -30,6 +31,7 @@
interactor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
shadeSceneViewModel = shadeSceneViewModel,
+ headsUpNotificationInteractor = headsUpNotificationInteractor,
featureFlags = featureFlagsClassic,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelKosmos.kt
new file mode 100644
index 0000000..ea79edc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.systemui.statusbar.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
+import com.android.systemui.statusbar.policy.batteryController
+
+val Kosmos.keyguardStatusBarViewModel: KeyguardStatusBarViewModel by
+ Kosmos.Fixture {
+ KeyguardStatusBarViewModel(
+ applicationCoroutineScope,
+ headsUpNotificationInteractor,
+ sceneInteractor,
+ keyguardInteractor,
+ keyguardStatusBarInteractor,
+ batteryController,
+ )
+ }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 36d97f6..32491b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -47,6 +47,7 @@
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
@@ -3086,6 +3087,7 @@
scheduleUpdateClientsIfNeededLocked(userState, forceUpdate);
updateAccessibilityShortcutTargetsLocked(userState, HARDWARE);
updateAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
+ updateAccessibilityShortcutTargetsLocked(userState, GESTURE);
updateAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
// Update the capabilities before the mode because we will check the current mode is
// invalid or not..
@@ -3207,6 +3209,7 @@
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, HARDWARE);
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
+ somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, GESTURE);
somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
@@ -3750,23 +3753,19 @@
return;
}
- final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3);
- shortcutTypeAndShortcutSetting.add(
- new Pair<>(HARDWARE,
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
- shortcutTypeAndShortcutSetting.add(
- new Pair<>(SOFTWARE,
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS));
+ final List<Integer> shortcutTypes = new ArrayList<>(4);
+ shortcutTypes.add(HARDWARE);
+ shortcutTypes.add(SOFTWARE);
if (android.view.accessibility.Flags.a11yQsShortcut()) {
- shortcutTypeAndShortcutSetting.add(
- new Pair<>(QUICK_SETTINGS,
- Settings.Secure.ACCESSIBILITY_QS_TARGETS));
+ shortcutTypes.add(QUICK_SETTINGS);
+ }
+ if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+ shortcutTypes.add(GESTURE);
}
final ComponentName serviceName = service.getComponentName();
- for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
- int shortcutType = shortcutTypePair.first;
- String shortcutSettingName = shortcutTypePair.second;
+ for (Integer shortcutType: shortcutTypes) {
+ String shortcutSettingName = ShortcutUtils.convertToKey(shortcutType);
if (userState.removeShortcutTargetLocked(shortcutType, serviceName)) {
final Set<String> currentTargets = userState.getShortcutTargetsLocked(shortcutType);
persistColonDelimitedSetToSettingLocked(
@@ -4068,6 +4067,11 @@
boolean enable, @UserShortcutType int shortcutTypes,
@NonNull List<String> shortcutTargets, @UserIdInt int userId) {
enableShortcutsForTargets_enforcePermission();
+ if ((shortcutTypes & GESTURE) == GESTURE
+ && !android.provider.Flags.a11yStandaloneGestureEnabled()) {
+ throw new IllegalArgumentException(
+ "GESTURE type shortcuts are disabled by feature flag");
+ }
for (int shortcutType : USER_SHORTCUT_TYPES) {
if ((shortcutTypes & shortcutType) == shortcutType) {
enableShortcutForTargets(enable, shortcutType, shortcutTargets, userId);
@@ -5451,6 +5455,9 @@
private final Uri mAccessibilityButtonTargetsUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+ private final Uri mAccessibilityGestureTargetsUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
+
private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS);
@@ -5504,6 +5511,8 @@
contentResolver.registerContentObserver(
mAccessibilityButtonTargetsUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
+ mAccessibilityGestureTargetsUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mUserInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
@@ -5575,6 +5584,10 @@
if (readAccessibilityShortcutTargetsLocked(userState, SOFTWARE)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilityGestureTargetsUri.equals(uri)) {
+ if (readAccessibilityShortcutTargetsLocked(userState, GESTURE)) {
+ onUserStateChangedLocked(userState);
+ }
} else if (mUserNonInteractiveUiTimeoutUri.equals(uri)
|| mUserInteractiveUiTimeoutUri.equals(uri)) {
readUserRecommendedUiTimeoutSettingsLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 710bae9..7bcbc27 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -27,6 +27,12 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;
import android.accessibilityservice.AccessibilityService.SoftKeyboardShowMode;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -101,6 +107,7 @@
private final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>();
private final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
+ private final ArraySet<String> mAccessibilityGestureTargets = new ArraySet<>();
private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
/**
@@ -224,6 +231,7 @@
mTouchExplorationGrantedServices.clear();
mAccessibilityShortcutKeyTargets.clear();
mAccessibilityButtonTargets.clear();
+ mAccessibilityGestureTargets.clear();
mTargetAssignedToAccessibilityButton = null;
mIsTouchExplorationEnabled = false;
mServiceHandlesDoubleTap = false;
@@ -524,6 +532,20 @@
}
}
+ private void dumpShortcutTargets(
+ PrintWriter pw, @UserShortcutType int shortcutType, String name) {
+ pw.append(" ").append(name).append(":{");
+ ArraySet<String> targets = getShortcutTargetsInternalLocked(shortcutType);
+ int size = targets.size();
+ for (int i = 0; i < size; i++) {
+ if (i > 0) {
+ pw.append(", ");
+ }
+ pw.append(targets.valueAt(i));
+ }
+ pw.println("}");
+ }
+
void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.append("User state[");
pw.println();
@@ -553,30 +575,12 @@
.append(String.valueOf(mAlwaysOnMagnificationEnabled));
pw.append("}");
pw.println();
- pw.append(" shortcut key:{");
- int size = mAccessibilityShortcutKeyTargets.size();
- for (int i = 0; i < size; i++) {
- final String componentId = mAccessibilityShortcutKeyTargets.valueAt(i);
- pw.append(componentId);
- if (i + 1 < size) {
- pw.append(", ");
- }
- }
- pw.println("}");
- pw.append(" button:{");
- size = mAccessibilityButtonTargets.size();
- for (int i = 0; i < size; i++) {
- final String componentId = mAccessibilityButtonTargets.valueAt(i);
- pw.append(componentId);
- if (i + 1 < size) {
- pw.append(", ");
- }
- }
- pw.println("}");
+ dumpShortcutTargets(pw, HARDWARE, "shortcut key");
+ dumpShortcutTargets(pw, SOFTWARE, "button");
pw.append(" button target:{").append(mTargetAssignedToAccessibilityButton);
pw.println("}");
- pw.append(" qs shortcut targets:").append(mAccessibilityQsTargets.toString());
- pw.println();
+ dumpShortcutTargets(pw, GESTURE, "gesture");
+ dumpShortcutTargets(pw, QUICK_SETTINGS, "qs shortcut targets");
pw.append(" a11y tiles in QS panel:").append(mA11yTilesInQsPanel.toString());
pw.println();
pw.append(" Bound services:{");
@@ -782,15 +786,17 @@
}
private ArraySet<String> getShortcutTargetsInternalLocked(@UserShortcutType int shortcutType) {
- if (shortcutType == UserShortcutType.HARDWARE) {
+ if (shortcutType == HARDWARE) {
return mAccessibilityShortcutKeyTargets;
- } else if (shortcutType == UserShortcutType.SOFTWARE) {
+ } else if (shortcutType == SOFTWARE) {
return mAccessibilityButtonTargets;
- } else if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+ } else if (shortcutType == GESTURE) {
+ return mAccessibilityGestureTargets;
+ } else if (shortcutType == QUICK_SETTINGS) {
return mAccessibilityQsTargets;
- } else if ((shortcutType == UserShortcutType.TRIPLETAP
+ } else if ((shortcutType == TRIPLETAP
&& isMagnificationSingleFingerTripleTapEnabledLocked()) || (
- shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP
+ shortcutType == TWOFINGER_DOUBLETAP
&& isMagnificationTwoFingerTripleTapEnabledLocked())) {
ArraySet<String> targets = new ArraySet<>();
targets.add(MAGNIFICATION_CONTROLLER_NAME);
@@ -811,7 +817,7 @@
*/
boolean updateShortcutTargetsLocked(
Set<String> newTargets, @UserShortcutType int shortcutType) {
- final int mask = UserShortcutType.TRIPLETAP | UserShortcutType.TWOFINGER_DOUBLETAP;
+ final int mask = TRIPLETAP | TWOFINGER_DOUBLETAP;
if ((shortcutType & mask) != 0) {
throw new IllegalArgumentException("Tap shortcuts cannot be updated with target sets.");
}
@@ -867,8 +873,8 @@
*/
public boolean removeShortcutTargetLocked(
@UserShortcutType int shortcutType, ComponentName target) {
- if (shortcutType == UserShortcutType.TRIPLETAP
- || shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
+ if (shortcutType == TRIPLETAP
+ || shortcutType == TWOFINGER_DOUBLETAP) {
throw new UnsupportedOperationException(
"removeShortcutTargetLocked only support shortcut type: "
+ "software and hardware and quick settings for now"
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f4b1229..779aabe 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -5559,8 +5559,9 @@
void noteAppKill(final ProcessRecord app, final @Reason int reason,
final @SubReason int subReason, final String msg) {
if (DEBUG_PROCESSES) {
- Slog.i(TAG, "note: " + app + " is being killed, reason: " + reason
- + ", sub-reason: " + subReason + ", message: " + msg);
+ Slog.i(TAG, "note: " + app + " is being killed, reason: "
+ + ApplicationExitInfo.reasonCodeToString(reason) + ", sub-reason: "
+ + ApplicationExitInfo.subreasonToString(subReason) + ", message: " + msg);
}
if (app.getPid() > 0 && !app.isolated && app.getDeathRecipient() != null) {
// We are killing it, put it into the dying process list.
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index cbea7aa..932b9c0 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -136,6 +136,7 @@
static final String[] sDeviceConfigAconfigScopes = new String[] {
"accessibility",
"android_core_networking",
+ "android_sdk",
"android_stylus",
"aoc",
"app_widgets",
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index b1a12f7..5d83ad6 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -26,6 +26,7 @@
import static android.app.AppOpsManager.FILTER_BY_UID;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
@@ -135,9 +136,10 @@
private static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
- + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + ","
- + OP_PHONE_CALL_CAMERA + "," + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + ","
- + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + "," + OP_RESERVED_FOR_TESTING;
+ + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
+ + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
+ + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+ + "," + OP_RESERVED_FOR_TESTING;
private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9732621..989a4d1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -708,10 +708,14 @@
// Streams currently muted by ringer mode and dnd
protected static volatile int sRingerAndZenModeMutedStreams;
- /** Streams that can be muted. Do not resolve to aliases when checking.
+ /** Streams that can be muted by system. Do not resolve to aliases when checking.
* @see System#MUTE_STREAMS_AFFECTED */
private int mMuteAffectedStreams;
+ /** Streams that can be muted by user. Do not resolve to aliases when checking.
+ * @see System#MUTE_STREAMS_AFFECTED */
+ private int mUserMutableStreams;
+
@NonNull
private SoundEffectsHelper mSfxHelper;
@@ -2346,6 +2350,7 @@
mMuteAffectedStreams &= ~(1 << vss.mStreamType);
}
}
+ updateUserMutableStreams();
}
private void createStreamStates() {
@@ -2415,6 +2420,8 @@
}
pw.print("\n- mute affected streams = 0x");
pw.println(Integer.toHexString(mMuteAffectedStreams));
+ pw.print("\n- user mutable streams = 0x");
+ pw.println(Integer.toHexString(mUserMutableStreams));
}
private void updateStreamVolumeAlias(boolean updateVolumes, String caller) {
@@ -2909,6 +2916,7 @@
mMuteAffectedStreams = mSettings.getSystemIntForUser(cr,
System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED,
UserHandle.USER_CURRENT);
+ updateUserMutableStreams();
updateMasterMono(cr);
@@ -2928,6 +2936,12 @@
mVolumeController.loadSettings(cr);
}
+ private void updateUserMutableStreams() {
+ mUserMutableStreams = mMuteAffectedStreams;
+ mUserMutableStreams &= ~(1 << AudioSystem.STREAM_VOICE_CALL);
+ mUserMutableStreams &= ~(1 << AudioSystem.STREAM_BLUETOOTH_SCO);
+ }
+
@GuardedBy("mSettingsLock")
private void resetActiveAssistantUidsLocked() {
mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS;
@@ -7142,6 +7156,11 @@
return (mMuteAffectedStreams & (1 << streamType)) != 0;
}
+ @Override
+ public boolean isStreamMutableByUi(int streamType) {
+ return (mUserMutableStreams & (1 << streamType)) != 0;
+ }
+
private void ensureValidDirection(int direction) {
switch (direction) {
case AudioManager.ADJUST_LOWER:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 275c930..0f9c344 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -67,7 +67,7 @@
/**
* Represent a logical device of type TV residing in Android system.
*/
-public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
+public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
private static final String TAG = "HdmiCecLocalDeviceTv";
// Whether ARC is available or not. "true" means that ARC is established between TV and
@@ -113,6 +113,18 @@
// handle.
private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
+ private boolean mWasActiveSourceSetToConnectedDevice = false;
+
+ @VisibleForTesting
+ protected boolean getWasActiveSourceSetToConnectedDevice() {
+ return mWasActiveSourceSetToConnectedDevice;
+ }
+
+ protected void setWasActiveSourceSetToConnectedDevice(
+ boolean wasActiveSourceSetToConnectedDevice) {
+ mWasActiveSourceSetToConnectedDevice = wasActiveSourceSetToConnectedDevice;
+ }
+
// Defines the callback invoked when TV input framework is updated with input status.
// We are interested in the notification for HDMI input addition event, in order to
// process any CEC commands that arrived before the input is added.
@@ -474,6 +486,7 @@
|| info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress,
HdmiControlManager.POWER_STATUS_ON);
+ setWasActiveSourceSetToConnectedDevice(true);
ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
} else {
@@ -489,13 +502,16 @@
protected int handleStandby(HdmiCecMessage message) {
assertRunOnServiceThread();
- // Ignore <Standby> from non-active source device.
- if (getActiveSource().logicalAddress != message.getSource()) {
+ // If a device has previously asserted the active source status, ignore <Standby> from
+ // non-active source.
+ if (getWasActiveSourceSetToConnectedDevice()
+ && getActiveSource().logicalAddress != message.getSource()) {
Slog.d(TAG, "<Standby> was not sent by the current active source, ignoring."
+ " Current active source has logical address "
+ getActiveSource().logicalAddress);
return Constants.HANDLED;
}
+ setWasActiveSourceSetToConnectedDevice(false);
return super.handleStandby(message);
}
@@ -1457,6 +1473,7 @@
invokeStandbyCompletedCallback(callback);
return;
}
+ setWasActiveSourceSetToConnectedDevice(false);
boolean sendStandbyOnSleep =
mService.getHdmiCecConfig().getIntValue(
HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e5dbce9..bef984b 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -53,6 +53,7 @@
import android.hardware.input.InputManager;
import android.hardware.input.InputSensorInfo;
import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGlyphMap;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.KeyboardLayoutSelectionResult;
import android.hardware.input.TouchCalibration;
@@ -311,6 +312,9 @@
// Manages Keyboard modifier keys remapping
private final KeyRemapper mKeyRemapper;
+ // Manages Keyboard glyphs for specific keyboards
+ private final KeyboardGlyphManager mKeyboardGlyphManager;
+
// Manages loading PointerIcons
private final PointerIconCache mPointerIconCache;
@@ -460,6 +464,7 @@
mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
mNative);
mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
+ mKeyboardGlyphManager = new KeyboardGlyphManager(mContext, injector.getLooper());
mPointerIconCache = new PointerIconCache(mContext, mNative);
mUseDevInputEventForAudioJack =
@@ -576,6 +581,7 @@
mKeyboardLedController.systemRunning();
mKeyRemapper.systemRunning();
mPointerIconCache.systemRunning();
+ mKeyboardGlyphManager.systemRunning();
}
private void reloadDeviceAliases() {
@@ -1208,6 +1214,11 @@
imeInfo, imeSubtype);
}
+ @Override // Binder call
+ public KeyGlyphMap getKeyGlyphMap(int deviceId) {
+ return mKeyboardGlyphManager.getKeyGlyphMap(deviceId);
+ }
+
public void setFocusedApplication(int displayId, InputApplicationHandle application) {
mNative.setFocusedApplication(displayId, application);
}
@@ -2077,6 +2088,7 @@
mBatteryController.dump(ipw);
mKeyboardBacklightController.dump(ipw);
mKeyboardLedController.dump(ipw);
+ mKeyboardGlyphManager.dump(ipw);
}
private void dumpAssociations(IndentingPrintWriter pw) {
diff --git a/services/core/java/com/android/server/input/KeyboardGlyphManager.java b/services/core/java/com/android/server/input/KeyboardGlyphManager.java
new file mode 100644
index 0000000..f59d72b
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardGlyphManager.java
@@ -0,0 +1,382 @@
+/*
+ * 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 com.android.server.input;
+
+import static com.android.hardware.input.Flags.keyboardGlyphMap;
+
+import android.annotation.AnyRes;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyGlyphMap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.InputDevice;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides information on custom glyphs configured for specific keyboard devices.
+ *
+ * @hide
+ */
+public final class KeyboardGlyphManager implements InputManager.InputDeviceListener {
+
+ private static final String TAG = "KeyboardGlyphManager";
+ // To enable these logs, run: 'adb shell setprop log.tag.KeyboardGlyphManager DEBUG'
+ // (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String TAG_KEYBOARD_GLYPH_MAPS = "keyboard-glyph-maps";
+ private static final String TAG_KEYBOARD_GLYPH_MAP = "keyboard-glyph-map";
+ private static final String TAG_KEY_GLYPH = "key-glyph";
+ private static final String TAG_MODIFIER_GLYPH = "modifier-glyph";
+ private static final String TAG_FUNCTION_ROW_KEY = "function-row-key";
+ private static final String TAG_HARDWARE_DEFINED_SHORTCUT = "hardware-defined-shortcut";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Object mGlyphMapLock = new Object();
+ @GuardedBy("mGlyphMapLock")
+ private boolean mGlyphMapDataLoaded = false;
+ @GuardedBy("mGlyphMapLock")
+ private List<KeyGlyphMapData> mGlyphMapDataList = new ArrayList<>();
+ // Cache for already loaded glyph maps
+ @GuardedBy("mGlyphMapLock")
+ private final SparseArray<KeyGlyphMap> mGlyphMapCache = new SparseArray<>();
+
+ KeyboardGlyphManager(Context context, Looper looper) {
+ mContext = context;
+ mHandler = new Handler(looper);
+ }
+
+ void systemRunning() {
+ if (!keyboardGlyphMap()) {
+ return;
+ }
+ // Listen to new Package installations to fetch new Keyboard glyph maps
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ resetMaps();
+ }
+ }, filter, null, mHandler);
+ }
+
+ @Override
+ @MainThread
+ public void onInputDeviceAdded(int deviceId) {
+ }
+
+ @Override
+ @MainThread
+ public void onInputDeviceRemoved(int deviceId) {
+ synchronized (mGlyphMapLock) {
+ mGlyphMapCache.remove(deviceId);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void onInputDeviceChanged(int deviceId) {
+ }
+
+ @MainThread
+ private void resetMaps() {
+ synchronized (mGlyphMapLock) {
+ mGlyphMapDataLoaded = false;
+ mGlyphMapDataList.clear();
+ mGlyphMapCache.clear();
+ }
+ }
+
+ @NonNull
+ private List<KeyGlyphMapData> loadGlyphMapDataList() {
+ final PackageManager pm = mContext.getPackageManager();
+ List<KeyGlyphMapData> glyphMaps = new ArrayList<>();
+ Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_GLYPH_MAPS);
+ for (ResolveInfo resolveInfo : pm.queryBroadcastReceiversAsUser(intent,
+ PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, UserHandle.USER_SYSTEM)) {
+ if (resolveInfo == null || resolveInfo.activityInfo == null) {
+ continue;
+ }
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ KeyGlyphMapData data = getKeyboardGlyphMapsInPackage(pm, activityInfo);
+ if (data == null) {
+ continue;
+ }
+ glyphMaps.add(data);
+ }
+ return glyphMaps;
+ }
+
+ @Nullable
+ private KeyGlyphMapData getKeyboardGlyphMapsInPackage(PackageManager pm,
+ @NonNull ActivityInfo receiver) {
+ Bundle metaData = receiver.metaData;
+ if (metaData == null) {
+ return null;
+ }
+
+ int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_GLYPH_MAPS);
+ if (configResId == 0) {
+ Slog.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_GLYPH_MAPS
+ + "' on receiver " + receiver.packageName + "/" + receiver.name);
+ return null;
+ }
+
+ try {
+ Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+ try (XmlResourceParser parser = resources.getXml(configResId)) {
+ XmlUtils.beginDocument(parser, TAG_KEYBOARD_GLYPH_MAPS);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+ if (!TAG_KEYBOARD_GLYPH_MAP.equals(element)) {
+ continue;
+ }
+ TypedArray a = resources.obtainAttributes(parser, R.styleable.KeyboardGlyphMap);
+ try {
+ int glyphMapRes = a.getResourceId(R.styleable.KeyboardGlyphMap_glyphMap, 0);
+ int vendor = a.getInt(R.styleable.KeyboardGlyphMap_vendorId, -1);
+ int product = a.getInt(R.styleable.KeyboardGlyphMap_productId, -1);
+ if (glyphMapRes != 0 && vendor != -1 && product != -1) {
+ return new KeyGlyphMapData(receiver.packageName, receiver.name,
+ glyphMapRes, vendor, product);
+ }
+ } finally {
+ a.recycle();
+ }
+ }
+ }
+ } catch (Exception ex) {
+ Slog.w(TAG, "Could not parse keyboard glyph map resource from receiver "
+ + receiver.packageName + "/" + receiver.name, ex);
+ }
+ return null;
+ }
+
+ @Nullable
+ private KeyGlyphMap loadGlyphMap(KeyGlyphMapData data) {
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ ComponentName componentName = new ComponentName(data.packageName, data.receiverName);
+ ActivityInfo receiver = pm.getReceiverInfo(componentName,
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+ return loadGlyphMapFromResource(resources, componentName, data.resourceId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package not found: " + e);
+ }
+ return null;
+ }
+
+ @NonNull
+ private KeyGlyphMap loadGlyphMapFromResource(Resources resources,
+ @NonNull ComponentName componentName, @AnyRes int glyphMapId) {
+ SparseIntArray keyGlyphs = new SparseIntArray();
+ SparseIntArray modifierGlyphs = new SparseIntArray();
+ List<Integer> functionRowKeys = new ArrayList<>();
+ HashMap<KeyGlyphMap.KeyCombination, Integer> hardwareShortcuts = new HashMap<>();
+ try {
+ XmlResourceParser parser = resources.getXml(glyphMapId);
+ XmlUtils.beginDocument(parser, TAG_KEYBOARD_GLYPH_MAP);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+ switch (element) {
+ case TAG_KEY_GLYPH -> {
+ final TypedArray a = resources.obtainAttributes(parser,
+ R.styleable.KeyGlyph);
+ try {
+ int keycode = a.getInt(R.styleable.KeyGlyph_keycode, 0);
+ int keyGlyph = a.getResourceId(R.styleable.KeyGlyph_glyphDrawable, 0);
+ if (keycode != 0 && keyGlyph != 0) {
+ keyGlyphs.put(keycode, keyGlyph);
+ }
+ } finally {
+ a.recycle();
+ }
+ }
+ case TAG_MODIFIER_GLYPH -> {
+ final TypedArray a = resources.obtainAttributes(parser,
+ R.styleable.ModifierGlyph);
+ try {
+ int modifier = a.getInt(R.styleable.ModifierGlyph_modifier, 0);
+ int modifierGlyph = a.getResourceId(
+ R.styleable.ModifierGlyph_glyphDrawable,
+ 0);
+ if (modifier != 0 && modifierGlyph != 0) {
+ modifierGlyphs.put(modifier, modifierGlyph);
+ }
+ } finally {
+ a.recycle();
+ }
+ }
+ case TAG_FUNCTION_ROW_KEY -> {
+ final TypedArray a = resources.obtainAttributes(parser,
+ R.styleable.FunctionRowKey);
+ try {
+ int keycode = a.getInt(R.styleable.FunctionRowKey_keycode, 0);
+ if (keycode != 0) {
+ functionRowKeys.add(keycode);
+ }
+ } finally {
+ a.recycle();
+ }
+ }
+ case TAG_HARDWARE_DEFINED_SHORTCUT -> {
+ final TypedArray a = resources.obtainAttributes(parser,
+ R.styleable.HardwareDefinedShortcut);
+ try {
+ int keycode = a.getInt(R.styleable.HardwareDefinedShortcut_keycode,
+ 0);
+ int modifierState = a.getInt(
+ R.styleable.HardwareDefinedShortcut_modifierState, 0);
+ int outKeycode = a.getInt(
+ R.styleable.HardwareDefinedShortcut_outKeycode,
+ 0);
+ if (keycode != 0 && modifierState != 0 && outKeycode != 0) {
+ hardwareShortcuts.put(
+ new KeyGlyphMap.KeyCombination(modifierState, keycode),
+ outKeycode);
+ }
+ } finally {
+ a.recycle();
+ }
+ }
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Log.e(TAG, "Unable to parse key glyph map : " + e);
+ }
+ return new KeyGlyphMap(componentName, keyGlyphs, modifierGlyphs,
+ functionRowKeys.stream().mapToInt(Integer::intValue).toArray(), hardwareShortcuts);
+ }
+
+ /**
+ * Returns keyboard glyph map corresponding to device ID
+ */
+ @Nullable
+ public KeyGlyphMap getKeyGlyphMap(int deviceId) {
+ if (!keyboardGlyphMap()) {
+ return null;
+ }
+ synchronized (mGlyphMapLock) {
+ if (mGlyphMapCache.indexOfKey(deviceId) >= 0) {
+ return mGlyphMapCache.get(deviceId);
+ }
+ KeyGlyphMap keyGlyphMap = getKeyGlyphMapInternal(deviceId);
+ mGlyphMapCache.put(deviceId, keyGlyphMap);
+ return keyGlyphMap;
+ }
+ }
+
+ @GuardedBy("mGlyphMapLock")
+ private KeyGlyphMap getKeyGlyphMapInternal(int deviceId) {
+ final InputDevice inputDevice = getInputDevice(deviceId);
+ if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+ return null;
+ }
+ if (!mGlyphMapDataLoaded) {
+ mGlyphMapDataList = loadGlyphMapDataList();
+ mGlyphMapDataLoaded = true;
+ }
+ for (KeyGlyphMapData data : mGlyphMapDataList) {
+ if (data.vendorId == inputDevice.getVendorId()
+ && data.productId == inputDevice.getProductId()) {
+ return loadGlyphMap(data);
+ }
+ }
+ return null;
+ }
+
+ void dump(IndentingPrintWriter ipw) {
+ if (!keyboardGlyphMap()) {
+ return;
+ }
+ List<KeyGlyphMapData> glyphMapDataList = loadGlyphMapDataList();
+ ipw.println(TAG + ": " + glyphMapDataList.size() + " glyph maps");
+ ipw.increaseIndent();
+ for (KeyGlyphMapData data : glyphMapDataList) {
+ ipw.println(data);
+ if (DEBUG) {
+ KeyGlyphMap map = loadGlyphMap(data);
+ if (map != null) {
+ ipw.increaseIndent();
+ ipw.println(map);
+ ipw.decreaseIndent();
+ }
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
+ @Nullable
+ private InputDevice getInputDevice(int deviceId) {
+ InputManager inputManager = mContext.getSystemService(InputManager.class);
+ return inputManager != null ? inputManager.getInputDevice(deviceId) : null;
+ }
+
+ private record KeyGlyphMapData(@NonNull String packageName, @NonNull String receiverName,
+ @AnyRes int resourceId, int vendorId, int productId) {
+ }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 97c32b9..4993412 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -255,17 +255,6 @@
}
}
- private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
- // Different languages are never compatible
- if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
- return false;
- }
- // If both the system and the keyboard layout have a country specifier, they must be equal.
- return TextUtils.isEmpty(systemLocale.getCountry())
- || TextUtils.isEmpty(keyboardLocale.getCountry())
- || systemLocale.getCountry().equals(keyboardLocale.getCountry());
- }
-
@MainThread
private void updateKeyboardLayouts() {
// Scan all input devices state for keyboard layouts that have been uninstalled.
@@ -953,21 +942,33 @@
return;
}
+ List<String> layoutNames = new ArrayList<>();
+ for (String layoutDesc : config.getConfiguredLayouts()) {
+ KeyboardLayout kl = getKeyboardLayout(layoutDesc);
+ if (kl == null) {
+ // b/349033234: Weird state with stale keyboard layout configured.
+ // Possibly due to race condition between KCM providing package being removed and
+ // corresponding layouts being removed from Datastore and cache.
+ // {@see updateKeyboardLayouts()}
+ //
+ // Ideally notification will be correctly shown after the keyboard layouts are
+ // configured again with the new package state.
+ return;
+ }
+ layoutNames.add(kl.getLabel());
+ }
showKeyboardLayoutNotification(
r.getString(
R.string.keyboard_layout_notification_selected_title,
inputDevice.getName()),
- createConfiguredNotificationText(mContext, config.getConfiguredLayouts()),
+ createConfiguredNotificationText(mContext, layoutNames),
inputDevice);
}
@MainThread
private String createConfiguredNotificationText(@NonNull Context context,
- @NonNull Set<String> selectedLayouts) {
+ @NonNull List<String> layoutNames) {
final Resources r = context.getResources();
- List<String> layoutNames = new ArrayList<>();
- selectedLayouts.forEach(
- (layoutDesc) -> layoutNames.add(getKeyboardLayout(layoutDesc).getLabel()));
Collections.sort(layoutNames);
switch (layoutNames.size()) {
case 1:
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index e1aa3a2..5ab493b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -96,6 +96,32 @@
@GuardedBy("ImfLock.class") private int mDisplayIdToShowIme = INVALID_DISPLAY;
@GuardedBy("ImfLock.class") private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
+ /**
+ * A set of status bits regarding the active IME.
+ *
+ * <p>This value is a combination of following two bits:</p>
+ * <dl>
+ * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
+ * <dd>
+ * If this bit is ON, connected IME is ready to accept touch/key events.
+ * </dd>
+ * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
+ * <dd>
+ * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
+ * </dd>
+ * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+ * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
+ * currently invisible.
+ * </dd>
+ * </dl>
+ * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
+ * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
+ */
+ @GuardedBy("ImfLock.class") private int mImeWindowVis;
+
+ @GuardedBy("ImfLock.class")
+ private int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+
@Nullable private CountDownLatch mLatchForTesting;
/**
@@ -473,7 +499,7 @@
if (getCurToken() != null) {
removeCurrentToken();
- mService.resetSystemUiLocked();
+ mService.resetSystemUiLocked(this);
mAutofillController.onResetSystemUi();
}
@@ -638,6 +664,36 @@
}
}
+ /**
+ * Returns the current {@link InputMethodSubtype}.
+ *
+ * <p>Also this method has had questionable behaviors:</p>
+ * <ul>
+ * <li>Calling this method can update {@link #mCurrentSubtype}.</li>
+ * <li>This method may return {@link #mCurrentSubtype} as-is, even if it does not belong to
+ * the current IME.</li>
+ * </ul>
+ * <p>TODO(b/347083680): Address above issues.</p>
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ InputMethodSubtype getCurrentInputMethodSubtype() {
+ final var selectedMethodId = getSelectedMethodId();
+ if (selectedMethodId == null) {
+ return null;
+ }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mUserId);
+ final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
+ if (imi == null || imi.getSubtypeCount() == 0) {
+ return null;
+ }
+ final var subtype = SubtypeUtils.getCurrentInputMethodSubtype(imi, settings,
+ mCurrentSubtype);
+ mCurrentSubtype = subtype;
+ return subtype;
+ }
+
+
@GuardedBy("ImfLock.class")
void setDisplayIdToShowIme(int displayId) {
mDisplayIdToShowIme = displayId;
@@ -662,4 +718,24 @@
int getUserId() {
return mUserId;
}
+
+ @GuardedBy("ImfLock.class")
+ void setImeWindowVis(int imeWindowVis) {
+ mImeWindowVis = imeWindowVis;
+ }
+
+ @GuardedBy("ImfLock.class")
+ int getImeWindowVis() {
+ return mImeWindowVis;
+ }
+
+ @GuardedBy("ImfLock.class")
+ int getBackDisposition() {
+ return mBackDisposition;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void setBackDisposition(int backDisposition) {
+ mBackDisposition = backDisposition;
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 78d501c..9c3b9d5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -552,33 +552,6 @@
@MultiUserUnawareField
boolean mIsInteractive = true;
- @MultiUserUnawareField
- int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
-
- /**
- * A set of status bits regarding the active IME.
- *
- * <p>This value is a combination of following two bits:</p>
- * <dl>
- * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
- * <dd>
- * If this bit is ON, connected IME is ready to accept touch/key events.
- * </dd>
- * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
- * <dd>
- * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
- * </dd>
- * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
- * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
- * currently invisible.
- * </dd>
- * </dl>
- * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
- * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
- */
- @MultiUserUnawareField
- int mImeWindowVis;
-
@SharedByAllUsersField
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
@@ -936,7 +909,9 @@
// Uh oh, current input method is no longer around!
// Pick another one...
Slog.i(TAG, "Current input method removed: " + curInputMethodId);
- updateSystemUiLocked(0 /* vis */, mBackDisposition);
+ final var bindingController = getInputMethodBindingController(userId);
+ updateSystemUiLocked(0 /* vis */,
+ bindingController.getBackDisposition(), userId);
if (!chooseNewDefaultIMELocked()) {
changed = true;
curIm = null;
@@ -1412,7 +1387,9 @@
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+ final var bindingController = getInputMethodBindingController(currentUserId);
+ updateSystemUiLocked(bindingController.getImeWindowVis(),
+ bindingController.getBackDisposition(), currentUserId);
mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
com.android.internal.R.bool.show_ongoing_ime_switcher);
if (mShowOngoingImeSwitcherForPhones) {
@@ -2426,11 +2403,13 @@
}
@GuardedBy("ImfLock.class")
- void resetSystemUiLocked() {
+ void resetSystemUiLocked(InputMethodBindingController bindingController) {
// Set IME window status as invisible when unbinding current method.
- mImeWindowVis = 0;
- mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+ final int imeWindowVis = 0;
+ final int backDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+ bindingController.setImeWindowVis(imeWindowVis);
+ bindingController.setBackDisposition(backDisposition);
+ updateSystemUiLocked(imeWindowVis, backDisposition, bindingController.getUserId());
}
@GuardedBy("ImfLock.class")
@@ -2544,7 +2523,12 @@
sessionState.mSession.finishSession();
} catch (RemoteException e) {
Slog.w(TAG, "Session failed to close due to remote exception", e);
- updateSystemUiLocked(0 /* vis */, mBackDisposition);
+ // TODO(b/350386877): Propagate userId from the caller or infer it from
+ // sessionState
+ final int userId = mCurrentUserId;
+ final var bindingController = getInputMethodBindingController(userId);
+ updateSystemUiLocked(0 /* vis */, bindingController.getBackDisposition(),
+ userId);
}
sessionState.mSession = null;
}
@@ -2764,8 +2748,8 @@
if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
return;
}
- mImeWindowVis = vis;
- mBackDisposition = backDisposition;
+ bindingController.setImeWindowVis(vis);
+ bindingController.setBackDisposition(backDisposition);
updateSystemUiLocked(vis, backDisposition, userId);
}
@@ -2802,23 +2786,23 @@
private void updateImeWindowStatus(boolean disableImeIcon) {
synchronized (ImfLock.class) {
+ // TODO(b/350386877): Propagate userId from the caller.
+ final int userId = mCurrentUserId;
if (disableImeIcon) {
- updateSystemUiLocked(0, mBackDisposition);
+ final var bindingController = getInputMethodBindingController(userId);
+ updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
} else {
- updateSystemUiLocked();
+ updateSystemUiLocked(userId);
}
}
}
- @GuardedBy("ImfLock.class")
- void updateSystemUiLocked() {
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
- }
-
// Caution! This method is called in this class. Handle multi-user carefully
@GuardedBy("ImfLock.class")
- private void updateSystemUiLocked(int vis, int backDisposition) {
- updateSystemUiLocked(vis, backDisposition, mCurrentUserId);
+ void updateSystemUiLocked(@UserIdInt int userId) {
+ final var bindingController = getInputMethodBindingController(userId);
+ updateSystemUiLocked(bindingController.getImeWindowVis(),
+ bindingController.getBackDisposition(), userId);
}
@GuardedBy("ImfLock.class")
@@ -3054,7 +3038,7 @@
// getCurrentInputMethodSubtype.
subtypeId = NOT_A_SUBTYPE_ID;
// TODO(b/347083680): The method below has questionable behaviors.
- newSubtype = getCurrentInputMethodSubtypeLocked();
+ newSubtype = bindingController.getCurrentInputMethodSubtype();
if (newSubtype != null) {
for (int i = 0; i < subtypeCount; ++i) {
if (Objects.equals(newSubtype, info.getSubtypeAt(i))) {
@@ -3068,7 +3052,8 @@
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true, userId);
IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+ updateSystemUiLocked(bindingController.getImeWindowVis(),
+ bindingController.getBackDisposition(), userId);
curMethod.changeInputMethodSubtype(newSubtype);
}
}
@@ -3455,7 +3440,7 @@
return;
}
mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
- updateSystemUiLocked();
+ updateSystemUiLocked(mCurrentUserId);
}
});
}
@@ -3621,29 +3606,30 @@
boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
+ final var bindingController = getInputMethodBindingController(userId);
if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
return false;
}
// There is a chance that IMM#hideSoftInput() is called in a transient state where
- // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
- // to be updated with the new value sent from IME process. Even in such a transient state
- // historically we have accepted an incoming call of IMM#hideSoftInput() from the
+ // IMMS#InputShown is already updated to be true whereas the user's ImeWindowVis is still
+ // waiting to be updated with the new value sent from IME process. Even in such a transient
+ // state historically we have accepted an incoming call of IMM#hideSoftInput() from the
// application process as a valid request, and have even promised such a behavior with CTS
// since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
IInputMethodInvoker curMethod = bindingController.getCurMethod();
final boolean shouldHideSoftInput = curMethod != null
- && (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+ && (isInputShownLocked()
+ || (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0);
mVisibilityStateComputer.requestImeVisibility(windowToken, false);
if (shouldHideSoftInput) {
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
- // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
+ // IMMS#mInputShown and the user's ImeWindowVis should be resolved spontaneously in
// the final state.
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason,
@@ -4633,8 +4619,8 @@
proto.write(HAVE_CONNECTION, bindingController.hasMainConnection());
proto.write(BOUND_TO_METHOD, userData.mBoundToMethod);
proto.write(IS_INTERACTIVE, mIsInteractive);
- proto.write(BACK_DISPOSITION, mBackDisposition);
- proto.write(IME_WINDOW_VISIBILITY, mImeWindowVis);
+ proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
+ proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard());
proto.end(token);
}
@@ -5143,18 +5129,18 @@
private void handleSetInteractive(final boolean interactive) {
synchronized (ImfLock.class) {
- mIsInteractive = interactive;
- updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition);
-
// TODO(b/305849394): Support multiple IMEs.
- final var userId = mCurrentUserId;
+ final int userId = mCurrentUserId;
+ final var bindingController = getInputMethodBindingController(userId);
+ mIsInteractive = interactive;
+ updateSystemUiLocked(
+ interactive ? bindingController.getImeWindowVis() : 0,
+ bindingController.getBackDisposition(), userId);
final var userData = getUserData(userId);
// Inform the current client of the change in active status
if (userData.mCurClient == null || userData.mCurClient.mClient == null) {
return;
}
- // TODO(b/325515685): user data must be retrieved by a userId parameter
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
bindingController.getCurMethodUid())) {
// Handle IME visibility when interactive changed before finishing the input to
@@ -5500,7 +5486,7 @@
newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
// If the subtype is not specified, choose the most applicable one
// TODO(b/347083680): The method below has questionable behaviors.
- newSubtype = getCurrentInputMethodSubtypeLocked();
+ newSubtype = bindingController.getCurrentInputMethodSubtype();
}
}
settings.putSelectedSubtype(newSubtypeHashcode);
@@ -5555,54 +5541,13 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mCurrentUserId == userId) {
- // TODO(b/347083680): The method below has questionable behaviors.
- return getCurrentInputMethodSubtypeLocked();
- }
-
- return InputMethodSettingsRepository.get(userId)
- .getCurrentInputMethodSubtypeForNonCurrentUsers();
+ final var bindingController = getInputMethodBindingController(userId);
+ // TODO(b/347083680): The method below has questionable behaviors.
+ return bindingController.getCurrentInputMethodSubtype();
}
}
/**
- * Returns the current {@link InputMethodSubtype} for the current user.
- *
- * <p>CAVEATS: You must also update
- * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}
- * when you update the algorithm of this method.</p>
- *
- * <p>TODO: Address code duplication between this and
- * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}.</p>
- *
- * <p>Also this method has had questionable behaviors:</p>
- * <ul>
- * <li>Calling this method can update {@link #mCurrentSubtype}.</li>
- * <li>This method may return {@link #mCurrentSubtype} as-is, even if it does not belong
- * to the current IME.</li>
- * </ul>
- * <p>TODO(b/347083680): Address above issues.</p>
- */
- @GuardedBy("ImfLock.class")
- InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
- final int userId = mCurrentUserId;
- final var selectedMethodId = getInputMethodBindingController(userId).getSelectedMethodId();
- if (selectedMethodId == null) {
- return null;
- }
- final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
- if (imi == null || imi.getSubtypeCount() == 0) {
- return null;
- }
- final var bindingController = getInputMethodBindingController(userId);
- final var subtype = SubtypeUtils.getCurrentInputMethodSubtype(imi, settings,
- bindingController.getCurrentSubtype());
- bindingController.setCurrentSubtype(subtype);
- return subtype;
- }
-
- /**
* Returns the default {@link InputMethodInfo} for the specific userId.
*
* @param userId user ID to query
@@ -6173,7 +6118,19 @@
p.println(" hasMainConnection="
+ u.mBindingController.hasMainConnection());
p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound());
- p.println(" mSwitchingController:");
+ p.println(" boundToMethod=" + u.mBoundToMethod);
+ p.println(" curClient=" + u.mCurClient);
+ if (u.mCurEditorInfo != null) {
+ p.println(" curEditorInfo:");
+ u.mCurEditorInfo.dump(p, " ", false /* dumpExtras */);
+ } else {
+ p.println(" curEditorInfo: null");
+ }
+ p.println(" imeBindingState:");
+ u.mImeBindingState.dump(" ", p);
+ p.println(" enabledSession=" + u.mEnabledSession);
+ p.println(" inFullscreenMode=" + u.mInFullscreenMode);
+ p.println(" switchingController:");
u.mSwitchingController.dump(p, " ");
};
mUserDataRepository.forAllUserData(userDataDump);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 89a31e7..656c87d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -85,7 +85,7 @@
if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
final InputMethodSubtype currentSubtype =
- mService.getCurrentInputMethodSubtypeLocked();
+ bindingController.getCurrentInputMethodSubtype();
if (currentSubtype != null) {
final String curMethodId = bindingController.getSelectedMethodId();
final InputMethodInfo currentImi =
@@ -201,7 +201,7 @@
attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Select input method");
w.setAttributes(attrs);
- mService.updateSystemUiLocked();
+ mService.updateSystemUiLocked(userId);
mService.sendOnNavButtonFlagsChangedLocked();
mSwitchingDialog.show();
}
@@ -239,7 +239,9 @@
mSwitchingDialog = null;
mSwitchingDialogTitleView = null;
- mService.updateSystemUiLocked();
+ // TODO(b/305849394): Make InputMethodMenuController multi-user aware
+ final int userId = mService.getCurrentImeUserIdLocked();
+ mService.updateSystemUiLocked(userId);
mService.sendOnNavButtonFlagsChangedLocked();
mDialogBuilder = null;
mIms = null;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index 5569e1e..0152158 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -571,57 +571,6 @@
}
}
- /**
- * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
- * non-current users.
- *
- * <p>TODO: Address code duplication between this and
- * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
- *
- * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
- */
- @Nullable
- InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
- final String selectedMethodId = getSelectedInputMethod();
- if (selectedMethodId == null) {
- return null;
- }
- final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
- if (imi == null || imi.getSubtypeCount() == 0) {
- return null;
- }
-
- final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
- if (subtypeHashCode != INVALID_SUBTYPE_HASHCODE) {
- final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
- subtypeHashCode);
- if (subtypeIndex >= 0) {
- return imi.getSubtypeAt(subtypeIndex);
- }
- }
-
- // If there are no selected subtypes, the framework will try to find the most applicable
- // subtype from explicitly or implicitly enabled subtypes.
- final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- getEnabledInputMethodSubtypeList(imi, true);
- // If there is only one explicitly or implicitly enabled subtype, just returns it.
- if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
- return null;
- }
- if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
- return explicitlyOrImplicitlyEnabledSubtypes.get(0);
- }
- final String locale = SystemLocaleWrapper.get(mUserId).get(0).toString();
- final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtype(
- explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
- locale, true);
- if (subtype != null) {
- return subtype;
- }
- return SubtypeUtils.findLastResortApplicableSubtype(
- explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
- }
-
@NonNull
AdditionalSubtypeMap getNewAdditionalSubtypeMap(@NonNull String imeId,
@NonNull ArrayList<InputMethodSubtype> subtypes,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 00e9d8d..47a79a3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1060,7 +1060,10 @@
final boolean isInstallDpcPackagesPermissionGranted = (snapshot.checkUidPermission(
android.Manifest.permission.INSTALL_DPC_PACKAGES, mInstallerUid)
== PackageManager.PERMISSION_GRANTED);
- final int targetPackageUid = snapshot.getPackageUid(packageName, 0, userId);
+ // Also query the package uid for archived packages, so that the user confirmation
+ // dialog can be displayed for updating archived apps.
+ final int targetPackageUid = snapshot.getPackageUid(packageName,
+ PackageManager.MATCH_ARCHIVED_PACKAGES, userId);
final boolean isUpdate = targetPackageUid != -1 || isApexSession();
final InstallSourceInfo existingInstallSourceInfo = isUpdate
? snapshot.getInstallSourceInfo(packageName, userId)
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e0e8a03..e215ca3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5975,6 +5975,7 @@
@VisibleForTesting
void removeUserInfo(@UserIdInt int userId) {
synchronized (mUsersLock) {
+ UserManager.invalidateUserSerialNumberCache();
mUsers.remove(userId);
}
}
@@ -6400,6 +6401,7 @@
// Remove this user from the list
synchronized (mUsersLock) {
+ UserManager.invalidateUserSerialNumberCache();
mUsers.remove(userId);
mIsUserManaged.delete(userId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7d7592f..5f19924 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8179,8 +8179,7 @@
}
void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
- if (mAppCompatController.getAppCompatOverrides()
- .getAppCompatOrientationOverrides()
+ if (mAppCompatController.getAppCompatOrientationOverrides()
.shouldIgnoreRequestedOrientation(requestedOrientation)) {
return;
}
@@ -8559,7 +8558,7 @@
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM
- && mLetterboxUiController.getFreeformCameraCompatMode()
+ && mAppCompatController.getAppCompatCameraOverrides().getFreeformCameraCompatMode()
!= CAMERA_COMPAT_FREEFORM_NONE;
// Bubble activities should always fill their parent and should not be letterboxed.
@@ -9887,7 +9886,8 @@
return mLetterboxUiController.getUserMinAspectRatio();
}
if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
- && !mLetterboxUiController.shouldOverrideMinAspectRatioForCamera()) {
+ && !mAppCompatController.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera()) {
return info.getMinAspectRatio();
}
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -10814,15 +10814,18 @@
proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
- mLetterboxUiController.shouldForceRotateForCameraCompat());
+ mAppCompatController.getAppCompatCameraOverrides()
+ .shouldForceRotateForCameraCompat());
proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
- mLetterboxUiController.shouldRefreshActivityForCameraCompat());
+ mAppCompatController.getAppCompatCameraOverrides()
+ .shouldRefreshActivityForCameraCompat());
proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
- mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat());
+ mAppCompatController.getAppCompatCameraOverrides()
+ .shouldRefreshActivityViaPauseForCameraCompat());
proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
mLetterboxUiController.shouldOverrideMinAspectRatio());
proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
- mAppCompatController.getAppCompatOverrides().getAppCompatOrientationOverrides()
+ mAppCompatController.getAppCompatOrientationOverrides()
.shouldIgnoreOrientationRequestLoop());
proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
mLetterboxUiController.shouldOverrideForceResizeApp());
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index 056c09e..0c32dfc 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -77,10 +77,10 @@
final boolean cycleThroughStop =
mWmService.mLetterboxConfiguration
.isCameraCompatRefreshCycleThroughStopEnabled()
- && !activity.mLetterboxUiController
- .shouldRefreshActivityViaPauseForCameraCompat();
+ && !activity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldRefreshActivityViaPauseForCameraCompat();
- activity.mLetterboxUiController.setIsRefreshRequested(true);
+ activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(true);
ProtoLog.v(WM_DEBUG_STATES,
"Refreshing activity for freeform camera compatibility treatment, "
+ "activityRecord=%s", activity);
@@ -97,24 +97,26 @@
}
}, REFRESH_CALLBACK_TIMEOUT_MS);
} catch (RemoteException e) {
- activity.mLetterboxUiController.setIsRefreshRequested(false);
+ activity.mAppCompatController.getAppCompatCameraOverrides()
+ .setIsRefreshRequested(false);
}
}
boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
- return activity.mLetterboxUiController.isRefreshRequested();
+ return activity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
}
void onActivityRefreshed(@NonNull ActivityRecord activity) {
// TODO(b/333060789): can we tell that refresh did not happen by observing the activity
// state?
- activity.mLetterboxUiController.setIsRefreshRequested(false);
+ activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(false);
}
private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
- && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+ && activity.mAppCompatController.getAppCompatOverrides()
+ .getAppCompatCameraOverrides().shouldRefreshActivityForCameraCompat()
&& ArrayUtils.find(mEvaluators.toArray(), evaluator ->
((Evaluator) evaluator)
.shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index dded1ca..ded205e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6192,11 +6192,14 @@
synchronized (mGlobalLockWithoutBoost) {
final WindowProcessController proc = mProcessNames.remove(name, uid);
if (proc != null && !mStartingProcessActivities.isEmpty()) {
- for (int i = mStartingProcessActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = mStartingProcessActivities.get(i);
+ // Use a copy in case finishIfPossible changes the list indirectly.
+ final ArrayList<ActivityRecord> activities =
+ new ArrayList<>(mStartingProcessActivities);
+ for (int i = activities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = activities.get(i);
if (uid == r.info.applicationInfo.uid && name.equals(r.processName)) {
Slog.w(TAG, proc + " is removed with pending start " + r);
- mStartingProcessActivities.remove(i);
+ mStartingProcessActivities.remove(r);
// If visible, finish it to avoid getting stuck on screen.
if (r.isVisibleRequested()) {
r.finishIfPossible("starting-proc-removed", false /* oomAdj */);
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
new file mode 100644
index 0000000..c0e5005
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -0,0 +1,235 @@
+/*
+ * 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.wm;
+
+import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
+
+import com.android.server.wm.utils.OptPropFactory;
+import com.android.window.flags.Flags;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Encapsulates app compat configurations and overrides related to camera.
+ */
+class AppCompatCameraOverrides {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME
+ ? "AppCompatCameraOverrides" : TAG_ATM;
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final AppCompatCameraOverridesState mAppCompatCameraOverridesState;
+ @NonNull
+ private final LetterboxConfiguration mLetterboxConfiguration;
+ @NonNull
+ private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mCameraCompatAllowRefreshOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mCameraCompatEnableRefreshViaPauseOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp;
+
+ AppCompatCameraOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull LetterboxConfiguration letterboxConfiguration,
+ @NonNull OptPropFactory optPropBuilder) {
+ mActivityRecord = activityRecord;
+ mLetterboxConfiguration = letterboxConfiguration;
+ mAppCompatCameraOverridesState = new AppCompatCameraOverridesState();
+ mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ final BooleanSupplier isCameraCompatTreatmentEnabled = AppCompatUtils.asLazy(
+ mLetterboxConfiguration::isCameraCompatTreatmentEnabled);
+ mCameraCompatAllowRefreshOptProp = optPropBuilder.create(
+ PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH,
+ isCameraCompatTreatmentEnabled);
+ mCameraCompatEnableRefreshViaPauseOptProp = optPropBuilder.create(
+ PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
+ isCameraCompatTreatmentEnabled);
+ mCameraCompatAllowForceRotationOptProp = optPropBuilder.create(
+ PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION,
+ isCameraCompatTreatmentEnabled);
+ }
+
+ /**
+ * Whether we should apply the min aspect ratio per-app override only when an app is connected
+ * to the camera.
+ * When this override is applied the min aspect ratio given in the app's manifest will be
+ * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
+ * is higher. The treatment will also apply if no value is provided in the manifest.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideMinAspectRatioForCamera() {
+ return mActivityRecord.isCameraActive()
+ && mAllowMinAspectRatioOverrideOptProp
+ .shouldEnableWithOptInOverrideAndOptOutProperty(
+ isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
+ }
+
+ /**
+ * Whether activity is eligible for activity "refresh" after camera compat force rotation
+ * treatment. See {@link DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity isn't opted out by the device manufacturer with override or by the app
+ * developers with the component property.
+ * </ul>
+ */
+ boolean shouldRefreshActivityForCameraCompat() {
+ return mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty(
+ isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
+ }
+
+ /**
+ * Whether activity should be "refreshed" after the camera compat force rotation treatment
+ * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
+ * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
+ * component property by the app developers.
+ * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
+ * manufacturer with override / by the app developers with the component property.
+ * </ul>
+ */
+ boolean shouldRefreshActivityViaPauseForCameraCompat() {
+ return mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty(
+ isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
+ }
+
+ /**
+ * Whether activity is eligible for camera compat force rotation treatment. See {@link
+ * DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity isn't opted out by the device manufacturer with override or by the app
+ * developers with the component property.
+ * </ul>
+ */
+ boolean shouldForceRotateForCameraCompat() {
+ return mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty(
+ isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
+ }
+
+ /**
+ * Whether activity is eligible for camera compatibility free-form treatment.
+ *
+ * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
+ * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
+ * provides changes to the camera and display orientation signals to match those expected on a
+ * portrait device in that orientation (for example, on a standard phone).
+ *
+ * <p>The treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Property gating the camera compatibility free-form treatment is enabled.
+ * <li>Activity isn't opted out by the device manufacturer with override.
+ * </ul>
+ */
+ boolean shouldApplyFreeformTreatmentForCameraCompat() {
+ return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled(
+ OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
+ }
+
+ /**
+ * @return {@code true} if the configuration needs to be recomputed after a camera state update.
+ */
+ boolean shouldRecomputeConfigurationForCameraCompat() {
+ return isOverrideOrientationOnlyForCameraEnabled()
+ || isCameraCompatSplitScreenAspectRatioAllowed()
+ || shouldOverrideMinAspectRatioForCamera();
+ }
+
+ boolean isOverrideOrientationOnlyForCameraEnabled() {
+ return isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
+ }
+
+ /**
+ * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
+ */
+ boolean isRefreshRequested() {
+ return mAppCompatCameraOverridesState.mIsRefreshRequested;
+ }
+
+ /**
+ * @param isRequested Whether activity "refresh" was requested but not finished
+ * in {@link #activityResumedLocked}.
+ */
+ void setIsRefreshRequested(boolean isRequested) {
+ mAppCompatCameraOverridesState.mIsRefreshRequested = isRequested;
+ }
+
+ /**
+ * Whether we use split screen aspect ratio for the activity when camera compat treatment
+ * is active because the corresponding config is enabled and activity supports resizing.
+ */
+ boolean isCameraCompatSplitScreenAspectRatioAllowed() {
+ return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
+ && !mActivityRecord.shouldCreateCompatDisplayInsets();
+ }
+
+ @FreeformCameraCompatMode
+ int getFreeformCameraCompatMode() {
+ return mAppCompatCameraOverridesState.mFreeformCameraCompatMode;
+ }
+
+ void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) {
+ mAppCompatCameraOverridesState.mFreeformCameraCompatMode = freeformCameraCompatMode;
+ }
+
+ private boolean isCompatChangeEnabled(long overrideChangeId) {
+ return mActivityRecord.info.isChangeEnabled(overrideChangeId);
+ }
+
+ static class AppCompatCameraOverridesState {
+ // Whether activity "refresh" was requested but not finished in
+ // ActivityRecord#activityResumedLocked following the camera compat force rotation in
+ // DisplayRotationCompatPolicy.
+ private boolean mIsRefreshRequested;
+
+ @FreeformCameraCompatMode
+ private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
new file mode 100644
index 0000000..ee523a2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -0,0 +1,49 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+
+/**
+ * Encapsulate the app compat logic related to camera.
+ */
+class AppCompatCameraPolicy {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME
+ ? "AppCompatCameraPolicy" : TAG_ATM;
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+
+ @NonNull
+ private final AppCompatCameraOverrides mAppCompatCameraOverrides;
+
+ AppCompatCameraPolicy(@NonNull ActivityRecord activityRecord,
+ @NonNull AppCompatCameraOverrides appCompatCameraOverrides) {
+ mActivityRecord = activityRecord;
+ mAppCompatCameraOverrides = appCompatCameraOverrides;
+ }
+
+ void recomputeConfigurationForCameraCompatIfNeeded() {
+ if (mAppCompatCameraOverrides.shouldRecomputeConfigurationForCameraCompat()) {
+ mActivityRecord.recomputeConfiguration();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 4b0d739..d8c0c17 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+
+import com.android.server.wm.utils.OptPropFactory;
/**
* Allows the interaction with all the app compat policies and configurations
@@ -28,19 +31,26 @@
private final AppCompatOrientationPolicy mOrientationPolicy;
@NonNull
private final AppCompatOverrides mAppCompatOverrides;
+ @NonNull
+ private final AppCompatCameraPolicy mAppCompatCameraPolicy;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
+ final PackageManager packageManager = wmService.mContext.getPackageManager();
+ final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
+ activityRecord.packageName);
mTransparentPolicy = new TransparentPolicy(activityRecord,
wmService.mLetterboxConfiguration);
- mAppCompatOverrides = new AppCompatOverrides(wmService, activityRecord,
- wmService.mLetterboxConfiguration);
+ mAppCompatOverrides = new AppCompatOverrides(activityRecord,
+ wmService.mLetterboxConfiguration, optPropBuilder);
// TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with aspectRatio.
final LetterboxUiController tmpController = activityRecord.mLetterboxUiController;
mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord,
mAppCompatOverrides, tmpController::shouldApplyUserFullscreenOverride,
tmpController::shouldApplyUserMinAspectRatioOverride,
tmpController::isSystemOverrideToFullscreenEnabled);
+ mAppCompatCameraPolicy = new AppCompatCameraPolicy(activityRecord,
+ mAppCompatOverrides.getAppCompatCameraOverrides());
}
@NonNull
@@ -54,7 +64,22 @@
}
@NonNull
+ AppCompatCameraPolicy getAppCompatCameraPolicy() {
+ return mAppCompatCameraPolicy;
+ }
+
+ @NonNull
AppCompatOverrides getAppCompatOverrides() {
return mAppCompatOverrides;
}
+
+ @NonNull
+ AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
+ return mAppCompatOverrides.getAppCompatOrientationOverrides();
+ }
+
+ @NonNull
+ AppCompatCameraOverrides getAppCompatCameraOverrides() {
+ return mAppCompatOverrides.getAppCompatCameraOverrides();
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index 446a75c..b0fdbb5 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -28,7 +28,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.AppCompatOverrides.asLazy;
+import static com.android.server.wm.AppCompatUtils.asLazy;
import android.annotation.NonNull;
import android.content.pm.ActivityInfo;
@@ -60,9 +60,9 @@
@NonNull
final OrientationOverridesState mOrientationOverridesState;
- AppCompatOrientationOverrides(@NonNull OptPropFactory optPropBuilder,
- @NonNull LetterboxConfiguration letterboxConfiguration,
- @NonNull ActivityRecord activityRecord) {
+ AppCompatOrientationOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull LetterboxConfiguration letterboxConfiguration,
+ @NonNull OptPropFactory optPropBuilder) {
mActivityRecord = activityRecord;
mOrientationOverridesState = new OrientationOverridesState(mActivityRecord,
System::currentTimeMillis);
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 8e9a9e9..960ef5a 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -103,11 +103,11 @@
return candidate;
}
- if (displayContent != null && mAppCompatOverrides
+ if (displayContent != null && mAppCompatOverrides.getAppCompatCameraOverrides()
.isOverrideOrientationOnlyForCameraEnabled()
- && (displayContent.mDisplayRotationCompatPolicy == null
- || !displayContent.mDisplayRotationCompatPolicy
- .isActivityEligibleForOrientationOverride(mActivityRecord))) {
+ && (displayContent.mDisplayRotationCompatPolicy == null
+ || !displayContent.mDisplayRotationCompatPolicy
+ .isActivityEligibleForOrientationOverride(mActivityRecord))) {
return candidate;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 794008a..c20da7c 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -19,22 +19,13 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
-import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
@@ -47,12 +38,8 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.NonNull;
-import android.content.pm.PackageManager;
import com.android.server.wm.utils.OptPropFactory;
-import com.android.window.flags.Flags;
-
-import java.util.function.BooleanSupplier;
/**
* Encapsulate logic related to operations guarded by an app override.
@@ -70,12 +57,6 @@
@NonNull
private final OptPropFactory.OptProp mFakeFocusOptProp;
@NonNull
- private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp;
- @NonNull
- private final OptPropFactory.OptProp mCameraCompatAllowRefreshOptProp;
- @NonNull
- private final OptPropFactory.OptProp mCameraCompatEnableRefreshViaPauseOptProp;
- @NonNull
private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
@NonNull
private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
@@ -87,37 +68,25 @@
private final OptPropFactory.OptProp mAllowUserAspectRatioOverrideOptProp;
@NonNull
private final OptPropFactory.OptProp mAllowUserAspectRatioFullscreenOverrideOptProp;
-
+ @NonNull
private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
+ @NonNull
+ private final AppCompatCameraOverrides mAppCompatCameraOverrides;
- AppCompatOverrides(@NonNull WindowManagerService wmService,
- @NonNull ActivityRecord activityRecord,
- @NonNull LetterboxConfiguration letterboxConfiguration) {
+ AppCompatOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull LetterboxConfiguration letterboxConfiguration,
+ @NonNull OptPropFactory optPropBuilder) {
mLetterboxConfiguration = letterboxConfiguration;
mActivityRecord = activityRecord;
- final PackageManager packageManager = wmService.mContext.getPackageManager();
- final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
- activityRecord.packageName);
-
- mAppCompatOrientationOverrides =
- new AppCompatOrientationOverrides(optPropBuilder, mLetterboxConfiguration,
- mActivityRecord);
+ mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
+ mLetterboxConfiguration, optPropBuilder);
+ mAppCompatCameraOverrides = new AppCompatCameraOverrides(mActivityRecord,
+ mLetterboxConfiguration, optPropBuilder);
mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS,
mLetterboxConfiguration::isCompatFakeFocusEnabled);
- final BooleanSupplier isCameraCompatTreatmentEnabled = asLazy(
- mLetterboxConfiguration::isCameraCompatTreatmentEnabled);
- mCameraCompatAllowForceRotationOptProp = optPropBuilder.create(
- PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION,
- isCameraCompatTreatmentEnabled);
- mCameraCompatAllowRefreshOptProp = optPropBuilder.create(
- PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH,
- isCameraCompatTreatmentEnabled);
- mCameraCompatEnableRefreshViaPauseOptProp = optPropBuilder.create(
- PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
- isCameraCompatTreatmentEnabled);
mAllowOrientationOverrideOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
@@ -162,6 +131,11 @@
return mAppCompatOrientationOverrides;
}
+ @NonNull
+ AppCompatCameraOverrides getAppCompatCameraOverrides() {
+ return mAppCompatCameraOverrides;
+ }
+
/**
* Whether sending compat fake focus for split screen resumed activities is enabled. Needed
* because some game engines wait to get focus before drawing the content of the app which isn't
@@ -179,57 +153,6 @@
isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS));
}
- /**
- * Whether activity is eligible for camera compat force rotation treatment. See {@link
- * DisplayRotationCompatPolicy} for context.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the camera compat treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override or by the app
- * developers with the component property.
- * </ul>
- */
- boolean shouldForceRotateForCameraCompat() {
- return mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
- }
-
- /**
- * Whether activity is eligible for activity "refresh" after camera compat force rotation
- * treatment. See {@link DisplayRotationCompatPolicy} for context.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the camera compat treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override or by the app
- * developers with the component property.
- * </ul>
- */
- boolean shouldRefreshActivityForCameraCompat() {
- return mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
- }
-
- /**
- * Whether activity should be "refreshed" after the camera compat force rotation treatment
- * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
- * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the camera compat treatment is enabled.
- * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
- * component property by the app developers.
- * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
- * manufacturer with override / by the app developers with the component property.
- * </ul>
- */
- boolean shouldRefreshActivityViaPauseForCameraCompat() {
- return mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
- }
-
boolean isSystemOverrideToFullscreenEnabled(int userAspectRatio) {
return isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER)
&& !mAllowOrientationOverrideOptProp.isFalse()
@@ -262,50 +185,6 @@
return isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
}
- boolean isOverrideOrientationOnlyForCameraEnabled() {
- return isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
- }
-
- /**
- * Whether activity is eligible for camera compatibility free-form treatment.
- *
- * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
- * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
- * provides changes to the camera and display orientation signals to match those expected on a
- * portrait device in that orientation (for example, on a standard phone).
- *
- * <p>The treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Property gating the camera compatibility free-form treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override.
- * </ul>
- */
- boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled(
- OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
- }
-
-
- /**
- * Whether we should apply the min aspect ratio per-app override only when an app is connected
- * to the camera.
- * When this override is applied the min aspect ratio given in the app's manifest will be
- * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
- * is higher. The treatment will also apply if no value is provided in the manifest.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideMinAspectRatioForCamera() {
- return mActivityRecord.isCameraActive()
- && mAllowMinAspectRatioOverrideOptProp
- .shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
- }
-
/**
* Whether should fix display orientation to landscape natural orientation when a task is
* fullscreen and the display is ignoring orientation requests.
@@ -382,21 +261,4 @@
private boolean isCompatChangeEnabled(long overrideChangeId) {
return mActivityRecord.info.isChangeEnabled(overrideChangeId);
}
-
- @NonNull
- static BooleanSupplier asLazy(@NonNull BooleanSupplier supplier) {
- return new BooleanSupplier() {
- private boolean mRead;
- private boolean mValue;
-
- @Override
- public boolean getAsBoolean() {
- if (!mRead) {
- mRead = true;
- mValue = supplier.getAsBoolean();
- }
- return mValue;
- }
- };
- }
}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
new file mode 100644
index 0000000..be51dd3b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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.wm;
+
+import android.annotation.NonNull;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Utilities for App Compat policies and overrides.
+ */
+class AppCompatUtils {
+
+ /**
+ * Lazy version of a {@link BooleanSupplier} which access an existing BooleanSupplier and
+ * caches the value.
+ *
+ * @param supplier The BooleanSupplier to decorate.
+ * @return A lazy implementation of a BooleanSupplier
+ */
+ @NonNull
+ static BooleanSupplier asLazy(@NonNull BooleanSupplier supplier) {
+ return new BooleanSupplier() {
+ private boolean mRead;
+ private boolean mValue;
+
+ @Override
+ public boolean getAsBoolean() {
+ if (!mRead) {
+ mRead = true;
+ mValue = supplier.getAsBoolean();
+ }
+ return mValue;
+ }
+ };
+ }
+}
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 9204017..68a4172 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -110,12 +110,14 @@
if (!isTreatmentEnabledForActivity(cameraActivity)) {
return false;
}
- final int existingCameraCompatMode =
- cameraActivity.mLetterboxUiController.getFreeformCameraCompatMode();
+ final int existingCameraCompatMode = cameraActivity.mAppCompatController
+ .getAppCompatCameraOverrides()
+ .getFreeformCameraCompatMode();
final int newCameraCompatMode = getCameraCompatMode(cameraActivity);
if (newCameraCompatMode != existingCameraCompatMode) {
mIsCameraCompatTreatmentPending = true;
- cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(newCameraCompatMode);
+ cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .setFreeformCameraCompatMode(newCameraCompatMode);
forceUpdateActivityAndTask(cameraActivity);
return true;
} else {
@@ -134,8 +136,8 @@
mDisplayContent.mDisplayId, cameraId);
return false;
}
- cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(
- CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
+ cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
forceUpdateActivityAndTask(cameraActivity);
mIsCameraCompatTreatmentPending = false;
return true;
@@ -191,6 +193,6 @@
|| mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
- return topActivity.mLetterboxUiController.isRefreshRequested();
+ return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index beb3767..3d71e95 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -228,9 +228,11 @@
!= lastReportedConfig.windowConfiguration.getDisplayRotation());
return isTreatmentEnabledForDisplay()
&& isTreatmentEnabledForActivity(activity)
- && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+ && activity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldRefreshActivityForCameraCompat()
&& (displayRotationChanged
- || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed());
+ || activity.mAppCompatController.getAppCompatCameraOverrides()
+ .isCameraCompatSplitScreenAspectRatioAllowed());
}
/**
@@ -254,7 +256,8 @@
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
&& isCameraActive(activity, /* mustBeFullscreen */ true)
- && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
+ && activity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldForceRotateForCameraCompat();
}
/**
@@ -286,7 +289,8 @@
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
- && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
+ && activity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldForceRotateForCameraCompat();
}
@Override
@@ -295,7 +299,8 @@
// Checking whether an activity in fullscreen rather than the task as this camera
// compat treatment doesn't cover activity embedding.
if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- cameraActivity.mLetterboxUiController.recomputeConfigurationForCameraCompatIfNeeded();
+ cameraActivity.mAppCompatController
+ .getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
mDisplayContent.updateOrientation();
return true;
}
@@ -362,7 +367,8 @@
|| topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
return true;
}
- topActivity.mLetterboxUiController.recomputeConfigurationForCameraCompatIfNeeded();
+ topActivity.mAppCompatController
+ .getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
mDisplayContent.updateOrientation();
return true;
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index f70d2a5..872b4e1 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
@@ -657,7 +656,6 @@
final boolean lastKeyguardGoingAway = mKeyguardGoingAway;
final ActivityRecord lastDismissKeyguardActivity = mDismissingKeyguardActivity;
- final ActivityRecord lastTurnScreenOnActivity = mTopTurnScreenOnActivity;
mRequestDismissKeyguard = false;
mOccluded = false;
@@ -666,7 +664,6 @@
mDismissingKeyguardActivity = null;
mTopTurnScreenOnActivity = null;
- boolean occludedByActivity = false;
final Task task = getRootTaskForControllingOccluding(display);
final ActivityRecord top = task != null ? task.getTopNonFinishingActivity() : null;
if (top != null) {
@@ -712,7 +709,7 @@
if (mTopTurnScreenOnActivity != null
&& !mService.mWindowManager.mPowerManager.isInteractive()
- && (mRequestDismissKeyguard || occludedByActivity)) {
+ && (mRequestDismissKeyguard || mOccluded)) {
controller.mTaskSupervisor.wakeUp("handleTurnScreenOn");
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 85eeab1..e924fb6 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
@@ -65,7 +64,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
-import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -113,18 +111,10 @@
@Nullable
private Letterbox mLetterbox;
- // Whether activity "refresh" was requested but not finished in
- // ActivityRecord#activityResumedLocked following the camera compat force rotation in
- // DisplayRotationCompatPolicy.
- private boolean mIsRefreshRequested;
-
private boolean mLastShouldShowLetterboxUi;
private boolean mDoubleTapEvent;
- @FreeformCameraCompatMode
- private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE;
-
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mLetterboxConfiguration = wmService.mLetterboxConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
@@ -147,13 +137,6 @@
}
}
-
- @VisibleForTesting
- int getSetOrientationRequestCounter() {
- return getAppCompatOverrides().getAppCompatOrientationOverrides()
- .getSetOrientationRequestCounter();
- }
-
/**
* Whether sending compat fake focus for split screen resumed activities is enabled. Needed
* because some game engines wait to get focus before drawing the content of the app which isn't
@@ -187,23 +170,6 @@
}
/**
- * Whether we should apply the min aspect ratio per-app override only when an app is connected
- * to the camera.
- * When this override is applied the min aspect ratio given in the app's manifest will be
- * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
- * is higher. The treatment will also apply if no value is provided in the manifest.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideMinAspectRatioForCamera() {
- return getAppCompatOverrides().shouldOverrideMinAspectRatioForCamera();
- }
-
- /**
* Whether we should apply the force resize per-app override. When this override is applied it
* forces the packages it is applied to to be resizable. It won't change whether the app can be
* put into multi-windowing mode, but allow the app to resize without going into size-compat
@@ -242,16 +208,6 @@
.setRelaunchingAfterRequestedOrientationChanged(isRelaunching);
}
- /**
- * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
- */
- boolean isRefreshRequested() {
- return mIsRefreshRequested;
- }
-
- void setIsRefreshRequested(boolean isRequested) {
- mIsRefreshRequested = isRequested;
- }
boolean isOverrideRespectRequestedOrientationEnabled() {
return getAppCompatOverrides().isOverrideRespectRequestedOrientationEnabled();
@@ -274,85 +230,6 @@
return getAppCompatOverrides().shouldUseDisplayLandscapeNaturalOrientation();
}
- boolean isOverrideOrientationOnlyForCameraEnabled() {
- return getAppCompatOverrides().isOverrideOrientationOnlyForCameraEnabled();
- }
-
- /**
- * Whether activity is eligible for activity "refresh" after camera compat force rotation
- * treatment. See {@link DisplayRotationCompatPolicy} for context.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the camera compat treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override or by the app
- * developers with the component property.
- * </ul>
- */
- boolean shouldRefreshActivityForCameraCompat() {
- return getAppCompatOverrides().shouldRefreshActivityForCameraCompat();
- }
-
- /**
- * Whether activity should be "refreshed" after the camera compat force rotation treatment
- * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
- * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the camera compat treatment is enabled.
- * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
- * component property by the app developers.
- * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
- * manufacturer with override / by the app developers with the component property.
- * </ul>
- */
- boolean shouldRefreshActivityViaPauseForCameraCompat() {
- return getAppCompatOverrides().shouldRefreshActivityViaPauseForCameraCompat();
- }
-
- /**
- * Whether activity is eligible for camera compat force rotation treatment. See {@link
- * DisplayRotationCompatPolicy} for context.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the camera compat treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override or by the app
- * developers with the component property.
- * </ul>
- */
- boolean shouldForceRotateForCameraCompat() {
- return getAppCompatOverrides().shouldForceRotateForCameraCompat();
- }
-
- /**
- * Whether activity is eligible for camera compatibility free-form treatment.
- *
- * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
- * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
- * provides changes to the camera and display orientation signals to match those expected on a
- * portrait device in that orientation (for example, on a standard phone).
- *
- * <p>The treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Property gating the camera compatibility free-form treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override.
- * </ul>
- */
- boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return getAppCompatOverrides().shouldApplyFreeformTreatmentForCameraCompat();
- }
-
- @FreeformCameraCompatMode
- int getFreeformCameraCompatMode() {
- return mFreeformCameraCompatMode;
- }
-
- void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) {
- mFreeformCameraCompatMode = freeformCameraCompatMode;
- }
-
private boolean isCompatChangeEnabled(long overrideChangeId) {
return mActivityRecord.info.isChangeEnabled(overrideChangeId);
}
@@ -574,27 +451,10 @@
: getDefaultMinAspectRatio();
}
- void recomputeConfigurationForCameraCompatIfNeeded() {
- if (isOverrideOrientationOnlyForCameraEnabled()
- || isCameraCompatSplitScreenAspectRatioAllowed()
- || shouldOverrideMinAspectRatioForCamera()) {
- mActivityRecord.recomputeConfiguration();
- }
- }
-
boolean isLetterboxEducationEnabled() {
return mLetterboxConfiguration.getIsEducationEnabled();
}
- /**
- * Whether we use split screen aspect ratio for the activity when camera compat treatment
- * is active because the corresponding config is enabled and activity supports resizing.
- */
- boolean isCameraCompatSplitScreenAspectRatioAllowed() {
- return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
- && !mActivityRecord.shouldCreateCompatDisplayInsets();
- }
-
private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
final boolean isBookMode = isDisplayFullScreenAndInPosture(/* isTabletop */ false);
final boolean isNotCenteredHorizontally = getHorizontalPositionMultiplier(
@@ -605,8 +465,9 @@
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
- || isCameraCompatSplitScreenAspectRatioAllowed()
- && getAppCompatOverrides().isCameraCompatTreatmentActive();
+ || mActivityRecord.mAppCompatController.getAppCompatCameraOverrides()
+ .isCameraCompatSplitScreenAspectRatioAllowed()
+ && getAppCompatOverrides().isCameraCompatTreatmentActive();
}
private float getDefaultMinAspectRatioForUnresizableApps() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 52a1ed9..f0b0e91 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3522,7 +3522,8 @@
appCompatTaskInfo.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top == null
? CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE
- : top.mLetterboxUiController.getFreeformCameraCompatMode();
+ : top.mAppCompatController.getAppCompatCameraOverrides()
+ .getFreeformCameraCompatMode();
}
/**
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 33ea9b4..9e46f2f 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static java.util.Objects.requireNonNull;
@@ -134,8 +135,10 @@
@Test
public void testApplyImeVisibility_hideImeExplicit() throws Exception {
- mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
synchronized (ImfLock.class) {
+ final var bindingController =
+ mInputMethodManagerService.getInputMethodBindingController(mUserId);
+ when(bindingController.getImeWindowVis()).thenReturn(IME_ACTIVE);
mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
STATE_HIDE_IME_EXPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
}
@@ -144,8 +147,10 @@
@Test
public void testApplyImeVisibility_hideNotAlways() throws Exception {
- mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
synchronized (ImfLock.class) {
+ final var bindingController =
+ mInputMethodManagerService.getInputMethodBindingController(mUserId);
+ when(bindingController.getImeWindowVis()).thenReturn(IME_ACTIVE);
mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
STATE_HIDE_IME_NOT_ALWAYS, eq(SoftInputShowHideReason.NOT_SET), mUserId);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 7b8b712..d070aaa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1700,10 +1700,12 @@
verify(mDisplayOffloadSession, never()).cancelBlockScreenOn();
}
- @RequiresFlagsEnabled(Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON)
@Test
public void testOffloadBlocker_turnON_thenOFF_cancelBlockScreenOnNotCalledIfUnblocked() {
// Set up.
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isOffloadSessionCancelBlockScreenOnEnabled())
+ .thenReturn(true);
int initState = Display.STATE_OFF;
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
@@ -1737,10 +1739,12 @@
verify(mDisplayOffloadSession, never()).cancelBlockScreenOn();
}
- @RequiresFlagsEnabled(Flags.FLAG_OFFLOAD_SESSION_CANCEL_BLOCK_SCREEN_ON)
@Test
public void testOffloadBlocker_turnON_thenOFF_cancelBlockScreenOn() {
// Set up.
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isOffloadSessionCancelBlockScreenOnEnabled())
+ .thenReturn(true);
int initState = Display.STATE_OFF;
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
diff --git a/services/tests/dreamservicetests/AndroidManifest.xml b/services/tests/dreamservicetests/AndroidManifest.xml
index 6092ef6..449521b 100644
--- a/services/tests/dreamservicetests/AndroidManifest.xml
+++ b/services/tests/dreamservicetests/AndroidManifest.xml
@@ -53,6 +53,35 @@
android:name="android.service.dream"
android:resource="@xml/test_dream_metadata_invalid" />
</service>
+
+ <service
+ android:name="com.android.server.dreams.TestDreamServiceWithNonexistentSettings"
+ android:exported="false"
+ android:label="Test Dream" >
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/test_dream_metadata_nonexistent_settings" />
+ </service>
+
+ <service
+ android:name="com.android.server.dreams.TestDreamServiceNoPackageNonexistentSettings"
+ android:exported="false"
+ android:label="Test Dream" >
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/test_dream_metadata_nopackage_nonexistent_settings" />
+ </service>
+
+ <activity android:name=".TestDreamSettingsActivity">
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml
new file mode 100644
index 0000000..f91f058
--- /dev/null
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<!-- The settings activity does not exist, which is invalid. -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.frameworks.dreamservicetests/.TestDreamSettingsActivityNonexistent"
+ android:showClockAndComplications="false"/>
diff --git a/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml
new file mode 100644
index 0000000..24032c9
--- /dev/null
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<!-- The settings activity's ComponentName is invalid. -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.frameworks.dreamservicetests.TestDreamSettingsActivityNonexistentNoPackage"
+ android:showClockAndComplications="false"/>
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index b98af6b..b4e1abf 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -91,6 +91,28 @@
}
@Test
+ public void testMetadataParsing_nonexistentSettingsActivity()
+ throws PackageManager.NameNotFoundException {
+ final String testDreamClassName =
+ "com.android.server.dreams.TestDreamServiceWithNonexistentSettings";
+ final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
+
+ assertThat(metadata.settingsActivity).isNull();
+ assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
+ }
+
+ @Test
+ public void testMetadataParsing_noPackage_nonexistentSettingsActivity()
+ throws PackageManager.NameNotFoundException {
+ final String testDreamClassName =
+ "com.android.server.dreams.TestDreamServiceNoPackageNonexistentSettings";
+ final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
+
+ assertThat(metadata.settingsActivity).isNull();
+ assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
+ }
+
+ @Test
public void testMetadataParsing_exceptionReading() {
final PackageManager packageManager = Mockito.mock(PackageManager.class);
final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java
new file mode 100644
index 0000000..bb8db8a
--- /dev/null
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java
@@ -0,0 +1,20 @@
+/*
+ * 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.dreams;
+
+public class TestDreamSettingsActivity {
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 123f0ed..ca30551 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -28,6 +28,7 @@
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
@@ -163,6 +164,7 @@
mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), HARDWARE);
mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), SOFTWARE);
+ mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), GESTURE);
mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
mUserState.setTouchExplorationEnabledLocked(true);
mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
@@ -186,6 +188,7 @@
assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(HARDWARE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(SOFTWARE).isEmpty());
+ assertTrue(mUserState.getShortcutTargetsLocked(GESTURE).isEmpty());
assertNull(mUserState.getTargetAssignedToAccessibilityButton());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 2a4b797..21364b8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2012,38 +2012,94 @@
}
@Test
- public void handleStandby_fromActiveSource_standby() {
- mPowerManager.setInteractive(true);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ public void handleStandby_fromActiveSource_previousActiveSourceSet_standby() {
+ mHdmiCecLocalDeviceTv = new MockTvDevice(mHdmiControlService);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x1000);
+ HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+ ADDR_TV);
mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+ .isFalse();
+ mPowerManager.setInteractive(true);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.dispatchMessage(activeSourceFromPlayback);
mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000,
"HdmiCecLocalDeviceTvTest");
mTestLooper.dispatchAll();
- HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
- ADDR_TV);
+ assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+ .isTrue();
assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mPowerManager.isInteractive()).isFalse();
+ assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+ .isFalse();
}
@Test
- public void handleStandby_fromNonActiveSource_noStandby() {
+ public void handleStandby_fromNonActiveSource_previousActiveSourceSet_noStandby() {
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_2, 0x2000);
+ HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+ ADDR_TV);
+ mHdmiCecLocalDeviceTv = new MockTvDevice(mHdmiControlService);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+ .isFalse();
mPowerManager.setInteractive(true);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+ mHdmiCecLocalDeviceTv.dispatchMessage(activeSourceFromPlayback);
mHdmiControlService.setActiveSource(ADDR_PLAYBACK_2, 0x2000,
"HdmiCecLocalDeviceTvTest");
mTestLooper.dispatchAll();
- HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
- ADDR_TV);
+ assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+ .isTrue();
assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mPowerManager.isInteractive()).isTrue();
}
+
+
+ @Test
+ public void handleStandby_fromNonActiveSource_previousActiveSourceNotSet_Standby() {
+ HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+ ADDR_TV);
+ mHdmiCecLocalDeviceTv = new MockTvDevice(mHdmiControlService);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+ .isFalse();
+ mPowerManager.setInteractive(true);
+
+ assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+ .isFalse();
+ assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ assertThat(mHdmiCecLocalDeviceTv.getWasActiveSourceSetToConnectedDevice())
+ .isFalse();
+ }
+
+ protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
+ MockTvDevice(HdmiControlService service) {
+ super(service);
+ }
+
+ @Override
+ protected int handleActiveSource(HdmiCecMessage message) {
+ setWasActiveSourceSetToConnectedDevice(true);
+ return super.handleActiveSource(message);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 37065fd..02d3b59 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1424,6 +1424,39 @@
@MediumTest
@Test
+ public void testSerialNumberAfterUserRemoval() {
+ final UserInfo user = mUserManager.createUser("Test User", 0);
+ assertThat(user).isNotNull();
+
+ final int userId = user.id;
+ assertThat(mUserManager.getUserSerialNumber(userId))
+ .isEqualTo(user.serialNumber);
+ mUsersToRemove.add(userId);
+ removeUser(userId);
+ int serialNumber = mUserManager.getUserSerialNumber(userId);
+ int timeout = REMOVE_USER_TIMEOUT_SECONDS * 5; // called every 200ms
+
+ // Wait for the user to be removed from memory
+ while(serialNumber > 0 && timeout > 0){
+ sleep(200);
+ timeout--;
+ serialNumber = mUserManager.getUserSerialNumber(userId);
+ }
+ assertThat(serialNumber).isEqualTo(-1);
+ }
+
+
+ private void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @MediumTest
+ @Test
public void testMaxUsers() {
int N = UserManager.getMaxSupportedUsers();
int count = mUserManager.getUsers().size();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 12ab3e1..a3252f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -103,8 +103,8 @@
@Test
public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
configureActivityAndDisplay();
- when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
- .thenReturn(false);
+ when(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldRefreshActivityForCameraCompat()).thenReturn(false);
mActivityRefresher.addEvaluator(mEvaluatorTrue);
mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
@@ -160,8 +160,9 @@
throws Exception {
configureActivityAndDisplay();
mActivityRefresher.addEvaluator(mEvaluatorTrue);
- doReturn(true).when(mActivity.mLetterboxUiController)
- .shouldRefreshActivityViaPauseForCameraCompat();
+ doReturn(true)
+ .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .shouldRefreshActivityViaPauseForCameraCompat();
mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
@@ -172,8 +173,9 @@
public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception {
configureActivityAndDisplay();
mActivityRefresher.addEvaluator(mEvaluatorTrue);
- doReturn(true).when(mActivity.mLetterboxUiController)
- .shouldRefreshActivityViaPauseForCameraCompat();
+ doReturn(true)
+ .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .shouldRefreshActivityViaPauseForCameraCompat();
mActivityRefresher.onActivityRefreshed(mActivity);
@@ -186,8 +188,8 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
- .setIsRefreshRequested(true);
+ verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
cycleThroughStop ? ON_STOP : ON_PAUSE);
@@ -211,9 +213,9 @@
.getTopMostActivity();
spyOn(mActivity.mLetterboxUiController);
- doReturn(true).when(
- mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
-
+ spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
doReturn(true).when(mActivity).inFreeformWindowingMode();
+ doReturn(true).when(mActivity.mAppCompatController
+ .getAppCompatCameraOverrides()).shouldRefreshActivityForCameraCompat();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 5f2853a..467050e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -149,6 +149,10 @@
.mLetterboxUiController).shouldApplyUserMinAspectRatioOverride();
}
+ void setShouldCreateCompatDisplayInsets(boolean enabled) {
+ doReturn(enabled).when(mActivityStack.top()).shouldCreateCompatDisplayInsets();
+ }
+
void setShouldApplyUserFullscreenOverride(boolean enabled) {
doReturn(enabled).when(mActivityStack.top()
.mLetterboxUiController).shouldApplyUserFullscreenOverride();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
new file mode 100644
index 0000000..9263b4f
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.testng.Assert;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatCameraOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatCameraOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatCameraOverridesTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(false);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+ public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+ public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityForCameraCompat(false);
+ });
+ }
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityForCameraCompat(false);
+ });
+ }
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityForCameraCompat(true);
+ });
+ }
+
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(false);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityViaPauseForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityViaPauseForCameraCompat(true);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_propertyFalseAndOverrideFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.prop().disable(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityViaPauseForCameraCompat(false);
+ });
+ }
+
+ @Test
+ public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.prop().enable(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldRefreshActivityViaPauseForCameraCompat(true);
+ });
+ }
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(false);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldForceRotateForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
+ public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldForceRotateForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
+ public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldForceRotateForCameraCompat(false);
+ });
+ }
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldForceRotateForCameraCompat(false);
+ });
+ }
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldForceRotateForCameraCompat(true);
+ });
+ }
+
+ @Test
+ @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public void testShouldApplyCameraCompatFreeformTreatment_flagIsDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
+ @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
+ @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA,
+ OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void testShouldRecomputeConfigurationForCameraCompat() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
+ robot.activity().createActivityWithComponentInNewTask();
+ robot.activateCamera(true);
+ robot.activity().setShouldCreateCompatDisplayInsets(false);
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<CameraOverridesRobotTest> consumer) {
+ spyOn(mWm.mLetterboxConfiguration);
+ final CameraOverridesRobotTest robot = new CameraOverridesRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class CameraOverridesRobotTest extends AppCompatRobotBase {
+
+ CameraOverridesRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ void activateCamera(boolean isCameraActive) {
+ doReturn(isCameraActive).when(activity().top()).isCameraActive();
+ }
+
+ void checkShouldRefreshActivityForCameraCompat(boolean expected) {
+ Assert.assertEquals(getAppCompatCameraOverrides()
+ .shouldRefreshActivityForCameraCompat(), expected);
+ }
+
+ void checkShouldRefreshActivityViaPauseForCameraCompat(boolean expected) {
+ Assert.assertEquals(getAppCompatCameraOverrides()
+ .shouldRefreshActivityViaPauseForCameraCompat(), expected);
+ }
+
+ void checkShouldForceRotateForCameraCompat(boolean expected) {
+ Assert.assertEquals(getAppCompatCameraOverrides()
+ .shouldForceRotateForCameraCompat(), expected);
+ }
+
+ void checkShouldApplyFreeformTreatmentForCameraCompat(boolean expected) {
+ Assert.assertEquals(getAppCompatCameraOverrides()
+ .shouldApplyFreeformTreatmentForCameraCompat(), expected);
+ }
+
+ private AppCompatCameraOverrides getAppCompatCameraOverrides() {
+ return activity().top().mAppCompatController.getAppCompatCameraOverrides();
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
new file mode 100644
index 0000000..4116313
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatCameraPolicy}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatCameraPolicyTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatCameraPolicyTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+ public void testRecomputeConfigurationForCameraCompatIfNeeded_allDisabledNoRecompute() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
+ robot.activateCamera(/* isCameraActive */ false);
+
+ robot.recomputeConfigurationForCameraCompatIfNeeded();
+ robot.checkRecomputeConfigurationInvoked(/* invoked */ false);
+
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+ public void testRecomputeConfigurationForCameraCompatIfNeeded_cameraEnabledRecompute() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
+ robot.activateCamera(/* isCameraActive */ false);
+
+ robot.recomputeConfigurationForCameraCompatIfNeeded();
+ robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+ public void testRecomputeConfigurationForCameraSplitScreenCompatIfNeeded_recompute() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
+ robot.activateCamera(/* isCameraActive */ false);
+
+ robot.recomputeConfigurationForCameraCompatIfNeeded();
+ robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void testRecomputeConfigurationForCameraSplitScreenCompatIfNeededWithCamera_recompute() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
+ robot.activateCamera(/* isCameraActive */ true);
+
+ robot.recomputeConfigurationForCameraCompatIfNeeded();
+ robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+ });
+ }
+
+ void runTestScenario(@NonNull Consumer<CameraPolicyRobotTest> consumer) {
+ spyOn(mWm.mLetterboxConfiguration);
+ final CameraPolicyRobotTest robot = new CameraPolicyRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class CameraPolicyRobotTest extends AppCompatRobotBase {
+
+ private final WindowManagerService mWm;
+
+ CameraPolicyRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ mWm = wm;
+ spyOn(mWm);
+ }
+
+ void activateCamera(boolean isCameraActive) {
+ doReturn(isCameraActive).when(activity().top()).isCameraActive();
+ }
+
+ void recomputeConfigurationForCameraCompatIfNeeded() {
+ getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
+ }
+
+ void checkRecomputeConfigurationInvoked(boolean invoked) {
+ if (invoked) {
+ verify(activity().top()).recomputeConfiguration();
+ } else {
+ verify(activity().top(), never()).recomputeConfiguration();
+ }
+ }
+
+ private AppCompatCameraPolicy getAppCompatCameraPolicy() {
+ return activity().top().mAppCompatController.getAppCompatCameraPolicy();
+ }
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java
index 2ef77f6..e1da913 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java
@@ -61,4 +61,11 @@
void enableUserAppAspectRatioSettings(boolean enabled) {
doReturn(enabled).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
}
+
+ void enableCameraCompatSplitScreenAspectRatio(boolean enabled) {
+ doReturn(enabled).when(mLetterboxConfiguration)
+ .isCameraCompatSplitScreenAspectRatioEnabled();
+ }
+
+
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index b3f1502..564c29f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -245,8 +245,8 @@
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(
- mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+ doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .shouldRefreshActivityForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
@@ -271,7 +271,7 @@
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mLetterboxUiController)
+ doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -305,6 +305,7 @@
.build();
spyOn(mActivity.mLetterboxUiController);
+ spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
spyOn(mActivity.info);
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
@@ -315,12 +316,14 @@
private void assertInCameraCompatMode() {
assertNotEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE,
- mActivity.mLetterboxUiController.getFreeformCameraCompatMode());
+ mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .getFreeformCameraCompatMode());
}
private void assertNotInCameraCompatMode() {
assertEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE,
- mActivity.mLetterboxUiController.getFreeformCameraCompatMode());
+ mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .getFreeformCameraCompatMode());
}
private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
@@ -329,8 +332,8 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
- .setIsRefreshRequested(true);
+ verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
cycleThroughStop ? ON_STOP : ON_PAUSE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index c65371f..d7814ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -266,7 +266,7 @@
public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mLetterboxUiController)
+ doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
.shouldForceRotateForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -468,8 +468,8 @@
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(
- mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+ doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .shouldRefreshActivityForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
@@ -494,8 +494,9 @@
public void testOnActivityConfigurationChanging_displayRotationNotChanging_noRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mLetterboxUiController)
- .isCameraCompatSplitScreenAspectRatioAllowed();
+ doReturn(false).when(mActivity
+ .mAppCompatController.getAppCompatCameraOverrides())
+ .isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ false);
@@ -507,7 +508,7 @@
public void testOnActivityConfigurationChanging_splitScreenAspectRatioAllowed_refresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mLetterboxUiController)
+ doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
.isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -533,7 +534,7 @@
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mLetterboxUiController)
+ doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -597,6 +598,7 @@
spyOn(mActivity.mAtmService.getLifecycleManager());
spyOn(mActivity.mLetterboxUiController);
+ spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
@@ -608,8 +610,8 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
- .setIsRefreshRequested(true);
+ verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
cycleThroughStop ? ON_STOP : ON_PAUSE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index bdd45c6..74e2d44 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -18,10 +18,6 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
@@ -31,9 +27,6 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
-import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
@@ -45,7 +38,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -54,8 +46,6 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -129,200 +119,7 @@
mController = new LetterboxUiController(mWm, mActivity);
}
- // shouldRefreshActivityForCameraCompat
- @Test
- public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() {
- doReturn(false).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
-
- assertFalse(mController.shouldRefreshActivityForCameraCompat());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
- public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
-
- assertFalse(mController.shouldRefreshActivityForCameraCompat());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
- public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
- throws Exception {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
- mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldRefreshActivityForCameraCompat());
- }
-
- @Test
- public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse()
- throws Exception {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
- mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ false);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldRefreshActivityForCameraCompat());
- }
-
- @Test
- public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue()
- throws Exception {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
- mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldRefreshActivityForCameraCompat());
- }
-
- // shouldRefreshActivityViaPauseForCameraCompat
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
- public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() {
- doReturn(false).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
-
- assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
- public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
-
- assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
- public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsFalseAndOverride_returnFalse()
- throws Exception {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
- mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ false);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
- }
-
- @Test
- public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue()
- throws Exception {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
- mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ true);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
- }
-
- // shouldForceRotateForCameraCompat
-
- @Test
- public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() {
- doReturn(false).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
-
- assertFalse(mController.shouldForceRotateForCameraCompat());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
- public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
-
- assertFalse(mController.shouldForceRotateForCameraCompat());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
- public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
- throws Exception {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
- mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldForceRotateForCameraCompat());
- }
-
- @Test
- public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse()
- throws Exception {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
- mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ false);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldForceRotateForCameraCompat());
- }
-
- @Test
- public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue()
- throws Exception {
- doReturn(true).when(mLetterboxConfiguration)
- .isCameraCompatTreatmentEnabled();
- mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldForceRotateForCameraCompat());
- }
-
- // shouldApplyFreeformTreatmentForCameraCompat
-
- @Test
- public void testShouldApplyCameraCompatFreeformTreatment_flagIsDisabled_returnsFalse() {
- mSetFlagsRule.disableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
-
- assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
- mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
-
- assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse()
- throws Exception {
- mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldApplyFreeformTreatmentForCameraCompat());
- }
-
- @Test
- public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue()
- throws Exception {
- mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldApplyFreeformTreatmentForCameraCompat());
- }
@Test
public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
@@ -641,39 +438,6 @@
assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
}
- @Test
- public void testRecomputeConfigurationForCameraCompatIfNeeded() {
- spyOn(mController);
- doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
- doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
- doReturn(false).when(mController).shouldOverrideMinAspectRatioForCamera();
- clearInvocations(mActivity);
-
- mController.recomputeConfigurationForCameraCompatIfNeeded();
-
- verify(mActivity, never()).recomputeConfiguration();
-
- // isOverrideOrientationOnlyForCameraEnabled
- doReturn(true).when(mController).isOverrideOrientationOnlyForCameraEnabled();
- clearInvocations(mActivity);
- mController.recomputeConfigurationForCameraCompatIfNeeded();
- verify(mActivity).recomputeConfiguration();
-
- // isCameraCompatSplitScreenAspectRatioAllowed
- doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
- doReturn(true).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
- clearInvocations(mActivity);
- mController.recomputeConfigurationForCameraCompatIfNeeded();
- verify(mActivity).recomputeConfiguration();
-
- // shouldOverrideMinAspectRatioForCamera
- doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
- doReturn(true).when(mController).shouldOverrideMinAspectRatioForCamera();
- clearInvocations(mActivity);
- mController.recomputeConfigurationForCameraCompatIfNeeded();
- verify(mActivity).recomputeConfiguration();
- }
-
private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
boolean orientationRequest) {
spyOn(mController);
@@ -875,7 +639,8 @@
doReturn(true).when(mActivity).isCameraActive();
mController = new LetterboxUiController(mWm, mActivity);
- assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+ assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera());
}
@Test
@@ -886,7 +651,8 @@
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
mController = new LetterboxUiController(mWm, mActivity);
- assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+ assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera());
}
@Test
@@ -897,7 +663,8 @@
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
mController = new LetterboxUiController(mWm, mActivity);
- assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera());
}
@Test
@@ -908,7 +675,8 @@
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
mController = new LetterboxUiController(mWm, mActivity);
- assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera());
}
@Test
@@ -917,7 +685,8 @@
doReturn(true).when(mActivity).isCameraActive();
mController = new LetterboxUiController(mWm, mActivity);
- assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera());
}
@Test
@@ -927,7 +696,8 @@
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
mController = new LetterboxUiController(mWm, mActivity);
- assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera());
}
@Test
@@ -938,7 +708,8 @@
doReturn(true).when(mActivity).isCameraActive();
mController = new LetterboxUiController(mWm, mActivity);
- assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index ef0aa9e..4bc87b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -2013,7 +2013,7 @@
public void getTaskInfoPropagatesCameraCompatMode() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopMostActivity();
- activity.mLetterboxUiController
+ activity.mAppCompatController.getAppCompatCameraOverrides()
.setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT);
assertEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT,
diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl
new file mode 100644
index 0000000..fe46db8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.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.telephony.satellite;
+
+parcelable ProvisionSubscriberId;
diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
new file mode 100644
index 0000000..796c82d
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
@@ -0,0 +1,141 @@
+/*
+ * 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 android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * ProvisionSubscriberId
+ *
+ * Satellite Gateway client will use these subscriber ids to register with satellite gateway service
+ * which identify user subscription with unique subscriber ids. These subscriber ids can be any
+ * unique value like iccid, imsi or msisdn which is decided based upon carrier requirements.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+public final class ProvisionSubscriberId implements Parcelable {
+ /** provision subscriberId */
+ @NonNull
+ private String mSubscriberId;
+
+ /** carrier id */
+ private int mCarrierId;
+
+ /**
+ * @hide
+ */
+ public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId) {
+ this.mCarrierId = carrierId;
+ this.mSubscriberId = subscriberId;
+ }
+
+ private ProvisionSubscriberId(Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mSubscriberId);
+ out.writeInt(mCarrierId);
+ }
+
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final @android.annotation.NonNull Creator<ProvisionSubscriberId> CREATOR =
+ new Creator<ProvisionSubscriberId>() {
+ @Override
+ public ProvisionSubscriberId createFromParcel(Parcel in) {
+ return new ProvisionSubscriberId(in);
+ }
+
+ @Override
+ public ProvisionSubscriberId[] newArray(int size) {
+ return new ProvisionSubscriberId[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ @Override
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @return token.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public String getSubscriberId() {
+ return mSubscriberId;
+ }
+
+ /**
+ * @return carrierId.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public int getCarrierId() {
+ return mCarrierId;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SubscriberId:");
+ sb.append(mSubscriberId);
+ sb.append(",");
+
+ sb.append("CarrierId:");
+ sb.append(mCarrierId);
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSubscriberId, mCarrierId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProvisionSubscriberId that = (ProvisionSubscriberId) o;
+ return mSubscriberId.equals(that.mSubscriberId) && mCarrierId
+ == that.mCarrierId;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mSubscriberId = in.readString();
+ mCarrierId = in.readInt();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 6caed14..b518c60d 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -50,6 +50,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -233,6 +234,29 @@
public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength";
/**
+ * Bundle key to get the response from
+ * {@link #requestProvisionSubscriberIds(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN =
+ "request_provision_subscriber_id";
+
+ /**
+ * Bundle key to get the response from
+ * {@link #requestIsProvisioned(String, Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_IS_SATELLITE_PROVISIONED = "request_is_satellite_provisioned";
+
+ /**
+ * Bundle key to get the response from
+ * {@link #provisionSatellite(List, Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite";
+
+
+ /**
* The request was successfully processed.
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -2580,6 +2604,179 @@
}
}
+ /**
+ * Request to get list of prioritized satellite subscriber ids to be used for provision.
+ *
+ * Satellite Gateway client will use these subscriber ids to register with satellite gateway
+ * service which identify user subscription with unique subscriber ids. These subscriber ids
+ * can be any unique value like iccid, imsi or msisdn which is decided based upon carrier
+ * requirements.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ * If successful, the callback returns a list of tokens sorted in ascending priority order index
+ * 0 has the highest priority. Otherwise, it returns an error with a SatelliteException.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public void requestProvisionSubscriberIds(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<List<ProvisionSubscriberId>, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) {
+ List<ProvisionSubscriberId> list =
+ Collections.singletonList(resultData.getParcelable(
+ KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN,
+ ProvisionSubscriberId.class));
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(list)));
+ } else {
+ loge("KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestProvisionSubscriberIds(receiver);
+ } else {
+ loge("requestProvisionSubscriberIds() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("requestProvisionSubscriberIds() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
+ /**
+ * Request to get provisioned status for given a satellite subscriber id.
+ *
+ * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check.
+ * @param executor The executor on which the callback will be called.
+ * @param callback callback.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public void requestIsProvisioned(@NonNull String satelliteSubscriberId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ Objects.requireNonNull(satelliteSubscriberId);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
+ boolean isIsProvisioned =
+ resultData.getBoolean(KEY_SATELLITE_PROVISIONED);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(isIsProvisioned)));
+ } else {
+ loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestIsProvisioned(satelliteSubscriberId, receiver);
+ } else {
+ loge("requestIsSatelliteProvisioned() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
+ /**
+ * Deliver the list of provisioned satellite subscriber ids.
+ *
+ * @param list List of ProvisionSubscriberId.
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public void provisionSatellite(@NonNull List<ProvisionSubscriberId> list,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_PROVISION_SATELLITE_TOKENS)) {
+ boolean isUpdated =
+ resultData.getBoolean(KEY_PROVISION_SATELLITE_TOKENS);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(isUpdated)));
+ } else {
+ loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.provisionSatellite(list, receiver);
+ } else {
+ loge("provisionSatellite() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("provisionSatellite() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
@Nullable
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
new file mode 100644
index 0000000..2dc8ffb
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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 android.telephony.satellite.stub;
+
+/**
+ * {@hide}
+ */
+parcelable ProvisionSubscriberId {
+ /** provision subscriberId */
+ String subscriberId;
+
+ /** carrier id */
+ int mCarrierId;
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9b01b71..7be52ea 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -78,6 +78,7 @@
import android.telephony.satellite.NtnSignalStrength;
import android.telephony.satellite.SatelliteCapabilities;
import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.ProvisionSubscriberId;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
@@ -3400,4 +3401,38 @@
* @hide
*/
void requestSatelliteSessionStats(int subId, in ResultReceiver receiver);
+
+ /**
+ * Request to get list of prioritized satellite subscriber ids to be used for provision.
+ *
+ * @param result The result receiver, which returns the list of prioritized satellite tokens
+ * to be used for provision if the request is successful or an error code if the request failed.
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestProvisionSubscriberIds(in ResultReceiver receiver);
+
+ /**
+ * Request to get provisioned status for given a satellite subscriber id.
+ *
+ * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check.
+ * @param result The result receiver, which returns the provisioned status of the token if the
+ * request is successful or an error code if the request failed.
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestIsProvisioned(in String satelliteSubscriberId, in ResultReceiver result);
+
+ /**
+ * Deliver the list of provisioned satellite subscriber ids.
+ *
+ * @param list List of provisioned satellite subscriber ids.
+ * @param result The result receiver that returns whether deliver success or fail.
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void provisionSatellite(in List<ProvisionSubscriberId> list, in ResultReceiver result);
}
diff --git a/tests/Input/res/drawable/test_key_drawable.xml b/tests/Input/res/drawable/test_key_drawable.xml
new file mode 100644
index 0000000..2addf8f
--- /dev/null
+++ b/tests/Input/res/drawable/test_key_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="4dp" />
+ <solid android:color="#ffffffff"/>
+</shape>
\ No newline at end of file
diff --git a/tests/Input/res/drawable/test_modifier_drawable.xml b/tests/Input/res/drawable/test_modifier_drawable.xml
new file mode 100644
index 0000000..2addf8f
--- /dev/null
+++ b/tests/Input/res/drawable/test_modifier_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="4dp" />
+ <solid android:color="#ffffffff"/>
+</shape>
\ No newline at end of file
diff --git a/tests/Input/res/xml/keyboard_glyph_maps.xml b/tests/Input/res/xml/keyboard_glyph_maps.xml
new file mode 100644
index 0000000..d0616ff
--- /dev/null
+++ b/tests/Input/res/xml/keyboard_glyph_maps.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<keyboard-glyph-maps xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <keyboard-glyph-map
+ androidprv:glyphMap="@xml/test_glyph_map"
+ androidprv:vendorId="0x1234"
+ androidprv:productId="0x3456" />
+</keyboard-glyph-maps>
\ No newline at end of file
diff --git a/tests/Input/res/xml/test_glyph_map.xml b/tests/Input/res/xml/test_glyph_map.xml
new file mode 100644
index 0000000..7a7c1ac
--- /dev/null
+++ b/tests/Input/res/xml/test_glyph_map.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<keyboard-glyph-map xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <key-glyph
+ androidprv:keycode="KEYCODE_BACK"
+ androidprv:glyphDrawable="@drawable/test_key_drawable" />
+ <modifier-glyph
+ androidprv:modifier="META"
+ androidprv:glyphDrawable="@drawable/test_modifier_drawable" />
+ <function-row-key androidprv:keycode="KEYCODE_EMOJI_PICKER" />
+ <hardware-defined-shortcut
+ androidprv:keycode="KEYCODE_1"
+ androidprv:modifierState="FUNCTION"
+ androidprv:outKeycode="KEYCODE_BACK" />
+ <hardware-defined-shortcut
+ androidprv:keycode="KEYCODE_2"
+ androidprv:modifierState="FUNCTION|META"
+ androidprv:outKeycode="KEYCODE_HOME" />
+</keyboard-glyph-map>
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
new file mode 100644
index 0000000..c073c7a
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
@@ -0,0 +1,197 @@
+/*
+ * 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 com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.hardware.input.KeyGlyphMap.KeyCombination
+import android.os.Bundle
+import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.hardware.input.Flags
+import com.android.test.input.R
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for custom keyboard glyph map configuration.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardGlyphManagerTests
+ */
+@Presubmit
+class KeyboardGlyphManagerTests {
+
+ companion object {
+ const val DEVICE_ID = 1
+ const val VENDOR_ID = 0x1234
+ const val PRODUCT_ID = 0x3456
+ const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
+ const val RECEIVER_NAME = "DummyReceiver"
+ }
+
+ @JvmField
+ @Rule(order = 0)
+ val setFlagsRule = SetFlagsRule()
+
+ @JvmField
+ @Rule(order = 1)
+ val mockitoRule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ @Mock
+ private lateinit var iInputManager: IInputManager
+
+ private lateinit var keyboardGlyphManager: KeyboardGlyphManager
+ private lateinit var context: Context
+ private lateinit var testLooper: TestLooper
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+ private lateinit var keyboardDevice: InputDevice
+
+ @Before
+ fun setup() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+ testLooper = TestLooper()
+ keyboardGlyphManager = KeyboardGlyphManager(context, testLooper.looper)
+
+ setupInputDevices()
+ setupBroadcastReceiver()
+ keyboardGlyphManager.systemRunning()
+ testLooper.dispatchAll()
+ }
+
+ @After
+ fun tearDown() {
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
+ }
+
+ private fun setupInputDevices() {
+ val inputManager = InputManager(context)
+ Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+
+ keyboardDevice = createKeyboard(DEVICE_ID, VENDOR_ID, PRODUCT_ID, 0, "", "")
+ Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+ Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+ }
+
+ private fun setupBroadcastReceiver() {
+ Mockito.`when`(context.packageManager).thenReturn(packageManager)
+
+ val info = createMockReceiver()
+ Mockito.`when`(packageManager.queryBroadcastReceiversAsUser(Mockito.any(), Mockito.anyInt(),
+ Mockito.anyInt())).thenReturn(listOf(info))
+ Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
+ .thenReturn(info.activityInfo)
+
+ val resources = context.resources
+ Mockito.`when`(
+ packageManager.getResourcesForApplication(
+ Mockito.any(
+ ApplicationInfo::class.java
+ )
+ )
+ ).thenReturn(resources)
+ }
+
+ private fun createMockReceiver(): ResolveInfo {
+ val info = ResolveInfo()
+ info.activityInfo = ActivityInfo()
+ info.activityInfo.packageName = PACKAGE_NAME
+ info.activityInfo.name = RECEIVER_NAME
+ info.activityInfo.applicationInfo = ApplicationInfo()
+ info.activityInfo.metaData = Bundle()
+ info.activityInfo.metaData.putInt(
+ InputManager.META_DATA_KEYBOARD_GLYPH_MAPS,
+ R.xml.keyboard_glyph_maps
+ )
+ info.serviceInfo = ServiceInfo()
+ info.serviceInfo.packageName = PACKAGE_NAME
+ info.serviceInfo.name = RECEIVER_NAME
+ return info
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYBOARD_GLYPH_MAP)
+ fun testGlyphMapsLoaded() {
+ assertNotNull(
+ "Glyph map for test keyboard(deviceId=$DEVICE_ID) must exist",
+ keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID)
+ )
+ assertNull(
+ "Glyph map for non-existing keyboard must be null",
+ keyboardGlyphManager.getKeyGlyphMap(-2)
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYBOARD_GLYPH_MAP)
+ fun testGlyphMapCorrectlyLoaded() {
+ val glyphMap = keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID)
+ // Test glyph map used in this test: {@see test_glyph_map.xml}
+ assertNotNull(glyphMap!!.getDrawableForKeycode(context, KeyEvent.KEYCODE_BACK))
+
+ assertNotNull(glyphMap.getDrawableForModifier(context, KeyEvent.KEYCODE_META_LEFT))
+ assertNotNull(glyphMap.getDrawableForModifier(context, KeyEvent.KEYCODE_META_RIGHT))
+
+ val functionRowKeys = glyphMap.functionRowKeys
+ assertEquals(1, functionRowKeys.size)
+ assertEquals(KeyEvent.KEYCODE_EMOJI_PICKER, functionRowKeys[0])
+
+ val hardwareShortcuts = glyphMap.hardwareShortcuts
+ assertEquals(2, hardwareShortcuts.size)
+ assertEquals(
+ KeyEvent.KEYCODE_BACK,
+ hardwareShortcuts[KeyCombination(KeyEvent.META_FUNCTION_ON, KeyEvent.KEYCODE_1)]
+ )
+ assertEquals(
+ KeyEvent.KEYCODE_HOME,
+ hardwareShortcuts[
+ KeyCombination(
+ KeyEvent.META_FUNCTION_ON or KeyEvent.META_META_ON,
+ KeyEvent.KEYCODE_2
+ )
+ ]
+ )
+ }
+}
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 93f97cb..301c0e6 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -58,7 +58,7 @@
import java.io.IOException
import java.io.InputStream
-private fun createKeyboard(
+fun createKeyboard(
deviceId: Int,
vendorId: Int,
productId: Int,
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index fff1dd1..0db0d95 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -16,6 +16,8 @@
package com.android.server.inputmethod.multisessiontest;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.getResponderUserId;
@@ -28,24 +30,33 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.UiAutomation;
import android.content.ComponentName;
+import android.content.Context;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import androidx.test.core.app.ActivityScenario;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+import java.util.List;
+
@RunWith(BedsteadJUnit4.class)
-@Ignore("b/345557347")
public final class ConcurrentMultiUserTest {
@ClassRule
@@ -55,6 +66,10 @@
private static final ComponentName TEST_ACTIVITY = new ComponentName(
getInstrumentation().getTargetContext().getPackageName(),
MainActivity.class.getName());
+ private final Context mContext = getInstrumentation().getTargetContext();
+ private final InputMethodManager mInputMethodManager =
+ mContext.getSystemService(InputMethodManager.class);
+ private final UiAutomation mUiAutomation = getInstrumentation().getUiAutomation();
private ActivityScenario<MainActivity> mActivityScenario;
private MainActivity mActivity;
@@ -69,10 +84,12 @@
// Launch driver activity.
mActivityScenario = ActivityScenario.launch(MainActivity.class);
mActivityScenario.onActivity(activity -> mActivity = activity);
+ mUiAutomation.adoptShellPermissionIdentity(INTERACT_ACROSS_USERS_FULL);
}
@After
public void tearDown() {
+ mUiAutomation.dropShellPermissionIdentity();
if (mActivityScenario != null) {
mActivityScenario.close();
}
@@ -87,6 +104,58 @@
assertPassengerImeHidden();
}
+ @Test
+ public void imeListNotEmpty() {
+ List<InputMethodInfo> driverImeList = mInputMethodManager.getInputMethodList();
+ assertWithMessage("Driver IME list shouldn't be empty")
+ .that(driverImeList.isEmpty()).isFalse();
+
+ List<InputMethodInfo> passengerImeList =
+ mInputMethodManager.getInputMethodListAsUser(mPeerUserId);
+ assertWithMessage("Passenger IME list shouldn't be empty")
+ .that(passengerImeList.isEmpty()).isFalse();
+ }
+
+ @Test
+ public void enabledImeListNotEmpty() {
+ List<InputMethodInfo> driverEnabledImeList =
+ mInputMethodManager.getEnabledInputMethodList();
+ assertWithMessage("Driver enabled IME list shouldn't be empty")
+ .that(driverEnabledImeList.isEmpty()).isFalse();
+
+ List<InputMethodInfo> passengerEnabledImeList =
+ mInputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(mPeerUserId));
+ assertWithMessage("Passenger enabled IME list shouldn't be empty")
+ .that(passengerEnabledImeList.isEmpty()).isFalse();
+ }
+
+ @Test
+ public void currentImeNotNull() {
+ InputMethodInfo driverIme = mInputMethodManager.getCurrentInputMethodInfo();
+ assertWithMessage("Driver IME shouldn't be null").that(driverIme).isNotNull();
+
+ InputMethodInfo passengerIme =
+ mInputMethodManager.getCurrentInputMethodInfoAsUser(UserHandle.of(mPeerUserId));
+ assertWithMessage("Passenger IME shouldn't be null")
+ .that(passengerIme).isNotNull();
+ }
+
+ @Test
+ public void enableDisableImePerUser() throws IOException {
+ UserHandle driver = UserHandle.of(mContext.getUserId());
+ UserHandle passenger = UserHandle.of(mPeerUserId);
+ enableDisableImeForUser(driver, passenger);
+ enableDisableImeForUser(passenger, driver);
+ }
+
+ @Test
+ public void setImePerUser() throws IOException {
+ UserHandle driver = UserHandle.of(mContext.getUserId());
+ UserHandle passenger = UserHandle.of(mPeerUserId);
+ setImeForUser(driver, passenger);
+ setImeForUser(passenger, driver);
+ }
+
private void assertDriverImeHidden() {
assertWithMessage("Driver IME should be hidden")
.that(mActivity.isMyImeVisible()).isFalse();
@@ -104,4 +173,89 @@
private void showDriverImeAndAssert() {
mActivity.showMyImeAndWait();
}
+
+ /**
+ * Disables/enables IME for {@code user1}, then verifies that the IME settings for {@code user1}
+ * has changed as expected and {@code user2} stays the same.
+ */
+ private void enableDisableImeForUser(UserHandle user1, UserHandle user2) throws IOException {
+ List<InputMethodInfo> user1EnabledImeList =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+ List<InputMethodInfo> user2EnabledImeList =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+
+ // Disable an IME for user1.
+ InputMethodInfo imeToDisable = user1EnabledImeList.get(0);
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime disable --user " + user1.getIdentifier() + " " + imeToDisable.getId());
+ List<InputMethodInfo> user1EnabledImeList2 =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+ List<InputMethodInfo> user2EnabledImeList2 =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+ assertWithMessage("User " + user1.getIdentifier() + " IME " + imeToDisable.getId()
+ + " should be disabled")
+ .that(user1EnabledImeList2.contains(imeToDisable)).isFalse();
+ assertWithMessage("Disabling user " + user1.getIdentifier()
+ + " IME shouldn't affect user " + user2.getIdentifier())
+ .that(user2EnabledImeList2.containsAll(user2EnabledImeList)
+ && user2EnabledImeList.containsAll(user2EnabledImeList2))
+ .isTrue();
+
+ // Enable the IME.
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime enable --user " + user1.getIdentifier() + " " + imeToDisable.getId());
+ List<InputMethodInfo> user1EnabledImeList3 =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+ List<InputMethodInfo> user2EnabledImeList3 =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+ assertWithMessage("User " + user1.getIdentifier() + " IME " + imeToDisable.getId()
+ + " should be enabled").that(user1EnabledImeList3.contains(imeToDisable)).isTrue();
+ assertWithMessage("Enabling user " + user1.getIdentifier()
+ + " IME shouldn't affect user " + user2.getIdentifier())
+ .that(user2EnabledImeList2.containsAll(user2EnabledImeList3)
+ && user2EnabledImeList3.containsAll(user2EnabledImeList2))
+ .isTrue();
+ }
+
+ /**
+ * Sets/resets IME for {@code user1}, then verifies that the IME settings for {@code user1}
+ * has changed as expected and {@code user2} stays the same.
+ */
+ private void setImeForUser(UserHandle user1, UserHandle user2) throws IOException {
+ // Reset IME for user1.
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime reset --user " + user1.getIdentifier());
+
+ List<InputMethodInfo> user1EnabledImeList =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+ assumeTrue("There must be at least two IME to test", user1EnabledImeList.size() >= 2);
+ InputMethodInfo user1Ime = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+ InputMethodInfo user2Ime = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+
+ // Set to another IME for user1.
+ InputMethodInfo anotherIme = null;
+ for (InputMethodInfo info : user1EnabledImeList) {
+ if (!info.equals(user1Ime)) {
+ anotherIme = info;
+ }
+ }
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime set --user " + user1.getIdentifier() + " " + anotherIme.getId());
+ InputMethodInfo user1Ime2 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+ InputMethodInfo user2Ime2 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+ assertWithMessage("The current IME for user " + user1.getIdentifier() + " is wrong")
+ .that(user1Ime2).isEqualTo(anotherIme);
+ assertWithMessage("The current IME for user " + user2.getIdentifier() + " shouldn't change")
+ .that(user2Ime2).isEqualTo(user2Ime);
+
+ // Reset IME for user1.
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime reset --user " + user1.getIdentifier());
+ InputMethodInfo user1Ime3 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+ InputMethodInfo user2Ime3 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+ assertWithMessage("The current IME for user " + user1.getIdentifier() + " is wrong")
+ .that(user1Ime3).isEqualTo(user1Ime);
+ assertWithMessage("The current IME for user " + user2.getIdentifier() + " shouldn't change")
+ .that(user2Ime3).isEqualTo(user2Ime);
+ }
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
index f126000..3c97b79 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -87,11 +87,13 @@
}
if (!mImm.showSoftInput(mEditor, /* flags= */ 0)) {
Log.e(TAG, String.format("Failed to show my IME as user %d, "
- + "mEditor:focused=%b,hasWindowFocus=%b", getUserId(),
+ + "mEditor:focused=%b,hasWindowFocus=%b",
+ Process.myUserHandle().getIdentifier(),
mEditor.isFocused(), mEditor.hasWindowFocus()));
}
});
PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> isMyImeVisible(),
- String.format("My IME (user %d) didn't show up", getUserId()));
+ String.format("My IME (user %d) didn't show up",
+ Process.myUserHandle().getIdentifier()));
}
}