Merge "Include the history tag string in checkin file verbatim" into udc-qpr-dev
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f53c087..8a4b6af 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2151,6 +2151,7 @@
public class SharedConnectivityManager {
method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
+ method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
method @Nullable public android.content.ServiceConnection getServiceConnection();
method public void setService(@Nullable android.os.IInterface);
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4dea4a7..b17dfa6 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -36,6 +36,7 @@
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -177,7 +178,7 @@
public @interface DataSyncTypes {}
/**
- * Used by {@link #enableSystemDataSync(int, int)}}.
+ * Used by {@link #enableSystemDataSyncForTypes(int, int)}}.
* Sync call metadata like muting, ending and silencing a call.
*
*/
@@ -529,6 +530,39 @@
}
/**
+ * @hide
+ */
+ public void enablePermissionsSync(int associationId) {
+ try {
+ mService.enablePermissionsSync(associationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void disablePermissionsSync(int associationId) {
+ try {
+ mService.disablePermissionsSync(associationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+ try {
+ return mService.getPermissionSyncRequest(associationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* <p>Calling this API requires a uses-feature
* {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
*
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index b5e2670..4d05a28 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -24,6 +24,7 @@
import android.companion.ISystemDataTransferCallback;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
/**
@@ -101,5 +102,11 @@
void disableSystemDataSync(int associationId, int flags);
+ void enablePermissionsSync(int associationId);
+
+ void disablePermissionsSync(int associationId);
+
+ PermissionSyncRequest getPermissionSyncRequest(int associationId);
+
void enableSecureTransport(boolean enabled);
}
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 7cb61fe..0a0071a 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -89,9 +89,10 @@
return view;
}
+ private final WeakReference<Context> mContext;
private final Resources mResources;
private final WeakReference<WindowManager> mWindowManager;
- private final WeakReference<AccessibilityManager> mAccessibilityManager;
+ private final IAccessibilityManager mAccessibilityManagerService;
private final INotificationManager mNotificationManager;
private final String mPackageName;
private final String mContextPackageName;
@@ -101,21 +102,14 @@
public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
INotificationManager notificationManager, String packageName) {
+ mContext = new WeakReference<>(context);
mResources = context.getResources();
mWindowManager = new WeakReference<>(context.getSystemService(WindowManager.class));
mNotificationManager = notificationManager;
mPackageName = packageName;
mContextPackageName = context.getPackageName();
mParams = createLayoutParams();
-
- // We obtain AccessibilityManager manually via its constructor instead of using method
- // AccessibilityManager.getInstance() for 2 reasons:
- // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
- // 2. getInstance() caches the instance for the process even if we pass a different
- // context to it. This is problematic for multi-user because callers can pass a context
- // created via Context.createContextAsUser().
- mAccessibilityManager = new WeakReference<>(
- new AccessibilityManager(context, accessibilityManager, context.getUserId()));
+ mAccessibilityManagerService = accessibilityManager;
}
public String getPackageName() {
@@ -306,11 +300,20 @@
* enabled.
*/
public void trySendAccessibilityEvent(View view, String packageName) {
- final AccessibilityManager accessibilityManager = mAccessibilityManager.get();
- if (accessibilityManager == null) {
+ final Context context = mContext.get();
+ if (context == null) {
return;
}
+ // We obtain AccessibilityManager manually via its constructor instead of using method
+ // AccessibilityManager.getInstance() for 2 reasons:
+ // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
+ // 2. getInstance() caches the instance for the process even if we pass a different
+ // context to it. This is problematic for multi-user because callers can pass a context
+ // created via Context.createContextAsUser().
+ final AccessibilityManager accessibilityManager = new AccessibilityManager(context,
+ mAccessibilityManagerService, context.getUserId());
+
if (!accessibilityManager.isEnabled()) {
accessibilityManager.removeClient();
return;
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 2ef923d..84a6c45 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -251,6 +251,20 @@
}
@Test
+ public void identifierToHalProgramIdentifier_withDeprecateDabId() {
+ long value = 0x98765ABCDL;
+ ProgramSelector.Identifier dabId = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, value);
+ ProgramIdentifier halDabIdExpected = AidlTestUtils.makeHalIdentifier(
+ IdentifierType.DAB_SID_EXT, 0x987650000ABCDL);
+
+ ProgramIdentifier halDabId = ConversionUtils.identifierToHalProgramIdentifier(dabId);
+
+ expect.withMessage("Converted 28-bit DAB identifier for HAL").that(halDabId)
+ .isEqualTo(halDabIdExpected);
+ }
+
+ @Test
public void identifierFromHalProgramIdentifier_withDabId() {
ProgramSelector.Identifier dabId =
ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_DAB_SID_EXT_ID);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 0112e32..15d14e8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -213,9 +213,6 @@
if (mRearDisplayStateRequest != null || isRearDisplayActive()) {
mRearDisplayStateRequest = null;
mDeviceStateManager.cancelStateRequest();
- } else {
- throw new IllegalStateException(
- "Unable to cancel a rear display session as there is no active session");
}
}
}
@@ -432,10 +429,6 @@
synchronized (mLock) {
if (mRearDisplayPresentationController != null) {
mDeviceStateManager.cancelStateRequest();
- } else {
- throw new IllegalStateException(
- "Unable to cancel a rear display presentation session as there is no "
- + "active session");
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 2eacaaf..6db2ae4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -17,7 +17,6 @@
package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY;
-
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.util.ExtensionHelper.isZero;
@@ -327,13 +326,17 @@
return features;
}
+ // We will transform the feature bounds to the Activity window, so using the rotation
+ // from the same source (WindowConfiguration) to make sure they are synchronized.
+ final int rotation = windowConfiguration.getDisplayRotation();
+
for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
continue;
}
Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
+ rotateRectToDisplayRotation(displayId, rotation, featureRect);
transformToWindowSpaceRect(windowConfiguration, featureRect);
if (isZero(featureRect)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 5bfb0ebd..a836e05 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -17,7 +17,6 @@
package androidx.window.sidecar;
import static android.view.Display.DEFAULT_DISPLAY;
-
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
@@ -120,10 +119,12 @@
}
List<SidecarDisplayFeature> features = new ArrayList<>();
+ final int rotation = activity.getResources().getConfiguration().windowConfiguration
+ .getDisplayRotation();
for (CommonFoldingFeature baseFeature : mStoredFeatures) {
SidecarDisplayFeature feature = new SidecarDisplayFeature();
Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
+ rotateRectToDisplayRotation(displayId, rotation, featureRect);
transformToWindowSpaceRect(activity, featureRect);
feature.setRect(featureRect);
feature.setType(baseFeature.getType());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 9e2611f..a08db79 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -16,21 +16,22 @@
package androidx.window.util;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import android.annotation.SuppressLint;
import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
+import android.util.RotationUtils;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.UiContext;
+import androidx.annotation.VisibleForTesting;
/**
* Util class for both Sidecar and Extensions.
@@ -44,47 +45,39 @@
/**
* Rotates the input rectangle specified in default display orientation to the current display
* rotation.
+ *
+ * @param displayId the display id.
+ * @param rotation the target rotation relative to the default display orientation.
+ * @param inOutRect the input/output Rect as specified in the default display orientation.
*/
- public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) {
- DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
- DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
- int rotation = displayInfo.rotation;
+ public static void rotateRectToDisplayRotation(
+ int displayId, @Surface.Rotation int rotation, @NonNull Rect inOutRect) {
+ final DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
+ final DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
- boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270;
- int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth;
- int displayHeight = isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight;
-
- inOutRect.intersect(0, 0, displayWidth, displayHeight);
-
- rotateBounds(inOutRect, displayWidth, displayHeight, rotation);
+ rotateRectToDisplayRotation(displayInfo, rotation, inOutRect);
}
- /**
- * Rotates the input rectangle within parent bounds for a given delta.
- */
- private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight,
- @Surface.Rotation int delta) {
- int origLeft = inOutRect.left;
- switch (delta) {
- case ROTATION_0:
- return;
- case ROTATION_90:
- inOutRect.left = inOutRect.top;
- inOutRect.top = parentWidth - inOutRect.right;
- inOutRect.right = inOutRect.bottom;
- inOutRect.bottom = parentWidth - origLeft;
- return;
- case ROTATION_180:
- inOutRect.left = parentWidth - inOutRect.right;
- inOutRect.right = parentWidth - origLeft;
- return;
- case ROTATION_270:
- inOutRect.left = parentHeight - inOutRect.bottom;
- inOutRect.bottom = inOutRect.right;
- inOutRect.right = parentHeight - inOutRect.top;
- inOutRect.top = origLeft;
- return;
- }
+ // We suppress the Lint error CheckResult for Rect#intersect because in case the displayInfo and
+ // folding features are out of sync, e.g. when a foldable devices is unfolding, it is acceptable
+ // to provide the original folding feature Rect even if they don't intersect.
+ @SuppressLint("RectIntersectReturnValueIgnored")
+ @VisibleForTesting
+ static void rotateRectToDisplayRotation(@NonNull DisplayInfo displayInfo,
+ @Surface.Rotation int rotation, @NonNull Rect inOutRect) {
+ // The inOutRect is specified in the default display orientation, so here we need to get
+ // the display width and height in the default orientation to perform the intersection and
+ // rotation.
+ final boolean isSideRotation =
+ displayInfo.rotation == ROTATION_90 || displayInfo.rotation == ROTATION_270;
+ final int baseDisplayWidth =
+ isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth;
+ final int baseDisplayHeight =
+ isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight;
+
+ inOutRect.intersect(0, 0, baseDisplayWidth, baseDisplayHeight);
+
+ RotationUtils.rotateBounds(inOutRect, baseDisplayWidth, baseDisplayHeight, rotation);
}
/** Transforms rectangle from absolute coordinate space to the window coordinate space. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index d189ae2..a0590dc 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -29,7 +29,7 @@
import org.junit.runner.RunWith;
/**
- * Test class for {@link WindowExtensionsTest}.
+ * Test class for {@link WindowExtensions}.
*
* Build/Install/Run:
* atest WMJetpackUnitTests:WindowExtensionsTest
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java
new file mode 100644
index 0000000..3278cdf
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.util;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link ExtensionHelper}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:ExtensionHelperTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExtensionHelperTest {
+
+ private static final int MOCK_DISPLAY_HEIGHT = 1000;
+ private static final int MOCK_DISPLAY_WIDTH = 2000;
+ private static final int MOCK_FEATURE_LEFT = 100;
+ private static final int MOCK_FEATURE_RIGHT = 200;
+
+ private static final int[] ROTATIONS = {
+ Surface.ROTATION_0,
+ Surface.ROTATION_90,
+ Surface.ROTATION_180,
+ Surface.ROTATION_270
+ };
+
+ private static final DisplayInfo[] MOCK_DISPLAY_INFOS = {
+ getMockDisplayInfo(Surface.ROTATION_0),
+ getMockDisplayInfo(Surface.ROTATION_90),
+ getMockDisplayInfo(Surface.ROTATION_180),
+ getMockDisplayInfo(Surface.ROTATION_270),
+ };
+
+ @Test
+ public void testRotateRectToDisplayRotation() {
+ for (int rotation : ROTATIONS) {
+ final Rect expectedResult = getExpectedFeatureRectAfterRotation(rotation);
+ // The method should return correctly rotated Rect even if the requested rotation value
+ // differs from the rotation in DisplayInfo. This is because the WindowConfiguration is
+ // not always synced with DisplayInfo.
+ for (DisplayInfo displayInfo : MOCK_DISPLAY_INFOS) {
+ final Rect rect = getMockFeatureRect();
+ ExtensionHelper.rotateRectToDisplayRotation(displayInfo, rotation, rect);
+ assertEquals(
+ "Result Rect should equal to expected for rotation: " + rotation
+ + "; displayInfo: " + displayInfo,
+ expectedResult, rect);
+ }
+ }
+ }
+
+ @NonNull
+ private static DisplayInfo getMockDisplayInfo(@Surface.Rotation int rotation) {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.rotation = rotation;
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ displayInfo.logicalWidth = MOCK_DISPLAY_WIDTH;
+ displayInfo.logicalHeight = MOCK_DISPLAY_HEIGHT;
+ } else {
+ displayInfo.logicalWidth = MOCK_DISPLAY_HEIGHT;
+ displayInfo.logicalHeight = MOCK_DISPLAY_WIDTH;
+ }
+ return displayInfo;
+ }
+
+ @NonNull
+ private static Rect getMockFeatureRect() {
+ return new Rect(MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT);
+ }
+
+ @NonNull
+ private static Rect getExpectedFeatureRectAfterRotation(@Surface.Rotation int rotation) {
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ return new Rect(
+ MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT);
+ case Surface.ROTATION_90:
+ return new Rect(0, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT,
+ MOCK_DISPLAY_HEIGHT, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT);
+ case Surface.ROTATION_180:
+ return new Rect(MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, 0,
+ MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT);
+ case Surface.ROTATION_270:
+ return new Rect(0, MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT,
+ MOCK_FEATURE_RIGHT);
+ default:
+ throw new IllegalArgumentException("Unknown rotation value: " + rotation);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index a11d952..bb65da3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -219,6 +219,13 @@
private ArrayList<TaskState> mPausingTasks = null;
/**
+ * List of tasks were pausing but closed in a subsequent merged transition. If a
+ * closing task is reopened, the leash is not initially hidden since it is already
+ * visible.
+ */
+ private ArrayList<TaskState> mClosingTasks = null;
+
+ /**
* List of tasks that we are switching to. Upon finish, these will remain visible and
* on top.
*/
@@ -369,6 +376,7 @@
}
mFinishTransaction = null;
mPausingTasks = null;
+ mClosingTasks = null;
mOpeningTasks = null;
mInfo = null;
mTransition = null;
@@ -413,6 +421,7 @@
mFinishCB = finishCB;
mFinishTransaction = finishT;
mPausingTasks = new ArrayList<>();
+ mClosingTasks = new ArrayList<>();
mOpeningTasks = new ArrayList<>();
mLeashMap = new ArrayMap<>();
mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
@@ -659,7 +668,10 @@
final TransitionInfo.Change change = closingTasks.get(i);
final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
- mPausingTasks.remove(pausingIdx);
+ // We are closing the pausing task, but it is still visible and can be
+ // restart by another transition prior to this transition finishing
+ final TaskState closingTask = mPausingTasks.remove(pausingIdx);
+ mClosingTasks.add(closingTask);
didMergeThings = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" closing pausing taskId=%d", change.getTaskInfo().taskId);
@@ -695,7 +707,12 @@
for (int i = 0; i < openingTasks.size(); ++i) {
final TransitionInfo.Change change = openingTasks.get(i);
final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
- int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+ final int closingIdx = TaskState.indexOf(mClosingTasks, change);
+ if (closingIdx >= 0) {
+ // Remove opening tasks from closing set
+ mClosingTasks.remove(closingIdx);
+ }
+ final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
// Something is showing/opening a previously-pausing app.
if (isLeaf) {
@@ -718,12 +735,14 @@
appearedTargets[nextTargetIdx++] = target;
// reparent into the original `mInfo` since that's where we are animating.
final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ final boolean wasClosing = closingIdx >= 0;
t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
t.setLayer(target.leash, layer);
- // Hide the animation leash, let listener show it.
- t.hide(target.leash);
+ // Hide the animation leash if not already visible, let listener show it
+ t.setVisibility(target.leash, !wasClosing);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " opening new leaf taskId=%d", target.taskId);
+ " opening new leaf taskId=%d wasClosing=%b",
+ target.taskId, wasClosing);
mOpeningTasks.add(new TaskState(change, target.leash));
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 097ed7a..93d7636 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -54,12 +54,13 @@
private static final String TAG = TaskViewTaskController.class.getSimpleName();
private final CloseGuard mGuard = new CloseGuard();
-
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /** Used to inset the activity content to allow space for a caption bar. */
+ private final Binder mCaptionInsetsOwner = new Binder();
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
private final TaskViewTransitions mTaskViewTransitions;
- private TaskViewBase mTaskViewBase;
private final Context mContext;
/**
@@ -70,21 +71,19 @@
* in this situation to allow us to notify listeners correctly if the task failed to open.
*/
private ActivityManager.RunningTaskInfo mPendingInfo;
- /* Indicates that the task we attempted to launch in the task view failed to launch. */
- private boolean mTaskNotFound;
+ private TaskViewBase mTaskViewBase;
protected ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
private SurfaceControl mTaskLeash;
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /* Indicates that the task we attempted to launch in the task view failed to launch. */
+ private boolean mTaskNotFound;
private boolean mSurfaceCreated;
private SurfaceControl mSurfaceControl;
private boolean mIsInitialized;
private boolean mNotifiedForInitialized;
+ private boolean mHideTaskWithSurface = true;
private TaskView.Listener mListener;
private Executor mListenerExecutor;
-
- /** Used to inset the activity content to allow space for a caption bar. */
- private final Binder mCaptionInsetsOwner = new Binder();
private Rect mCaptionInsets;
public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
@@ -102,6 +101,19 @@
mGuard.open("release");
}
+ /**
+ * Specifies if the task should be hidden when the surface is destroyed.
+ * <p>This is {@code true} by default.
+ *
+ * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the
+ * surface is destroyed, {@code true} otherwise.
+ */
+ public void setHideTaskWithSurface(boolean hideTaskWithSurface) {
+ // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks
+ // are moved to a window in SystemUI in auto.
+ mHideTaskWithSurface = hideTaskWithSurface;
+ }
+
SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
@@ -257,10 +269,15 @@
mTaskNotFound = false;
}
+ /** This method shouldn't be called when shell transitions are enabled. */
private void updateTaskVisibility() {
+ boolean visible = mSurfaceCreated;
+ if (!visible && !mHideTaskWithSurface) {
+ return;
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
- if (!mSurfaceCreated) {
+ wct.setHidden(mTaskToken, !visible /* hidden */);
+ if (!visible) {
wct.reorder(mTaskToken, false /* onTop */);
}
mSyncQueue.queue(wct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index d098d33..0088051 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -221,6 +221,20 @@
}
@Test
+ public void testSurfaceDestroyed_withTask_shouldNotHideTask_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
+ mTaskViewTaskController.setHideTaskWithSurface(false);
+
+ SurfaceHolder sh = mock(SurfaceHolder.class);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.surfaceCreated(sh);
+ reset(mViewListener);
+ mTaskView.surfaceDestroyed(sh);
+
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
public void testSurfaceDestroyed_withTask_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index 82facd0..8b5aeaa 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -27,4 +27,5 @@
android:gravity="center_vertical|start"
android:paddingStart="@dimen/battery_level_padding_start"
android:importantForAccessibility="no"
+ android:includeFontPadding="false"
/>
diff --git a/packages/SystemUI/res/layout/battery_status_chip.xml b/packages/SystemUI/res/layout/battery_status_chip.xml
index ff68ac0..7437183 100644
--- a/packages/SystemUI/res/layout/battery_status_chip.xml
+++ b/packages/SystemUI/res/layout/battery_status_chip.xml
@@ -25,7 +25,8 @@
<LinearLayout
android:id="@+id/rounded_container"
android:layout_width="wrap_content"
- android:layout_height="@dimen/ongoing_appops_chip_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/ongoing_appops_chip_height"
android:layout_gravity="center"
android:background="@drawable/statusbar_chip_bg"
android:clipToOutline="true"
@@ -36,7 +37,7 @@
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/battery_meter_view"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 5132e57..c5351b1 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -79,6 +79,7 @@
android:id="@+id/status_bar_start_side_except_heads_up"
android:layout_height="wrap_content"
android:layout_width="match_parent"
+ android:layout_gravity="center_vertical|start"
android:clipChildren="false">
<ViewStub
android:id="@+id/operator_name"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 8200e5c..9059230 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -17,6 +17,7 @@
package com.android.systemui.shared.rotation;
import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
@@ -37,6 +38,8 @@
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -67,6 +70,7 @@
import java.io.PrintWriter;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
@@ -119,6 +123,8 @@
private final int mIconCwStart0ResId;
@DrawableRes
private final int mIconCwStart90ResId;
+ /** Defaults to mainExecutor if not set via {@link #setBgExecutor(Executor)}. */
+ private Executor mBgExecutor;
@DrawableRes
private int mIconResId;
@@ -178,6 +184,8 @@
mAccessibilityManager = AccessibilityManager.getInstance(context);
mTaskStackListener = new TaskStackListenerImpl();
mWindowRotationProvider = windowRotationProvider;
+
+ mBgExecutor = context.getMainExecutor();
}
public void setRotationButton(RotationButton rotationButton,
@@ -193,6 +201,10 @@
return mContext;
}
+ public void setBgExecutor(Executor bgExecutor) {
+ mBgExecutor = bgExecutor;
+ }
+
/**
* Called during Taskbar initialization.
*/
@@ -219,8 +231,11 @@
mListenersRegistered = true;
- updateDockedState(mContext.registerReceiver(mDockedReceiver,
- new IntentFilter(Intent.ACTION_DOCK_EVENT)));
+ mBgExecutor.execute(() -> {
+ final Intent intent = mContext.registerReceiver(mDockedReceiver,
+ new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ mContext.getMainExecutor().execute(() -> updateDockedState(intent));
+ });
if (registerRotationWatcher) {
try {
@@ -246,11 +261,13 @@
mListenersRegistered = false;
- try {
- mContext.unregisterReceiver(mDockedReceiver);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Docked receiver already unregistered", e);
- }
+ mBgExecutor.execute(() -> {
+ try {
+ mContext.unregisterReceiver(mDockedReceiver);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Docked receiver already unregistered", e);
+ }
+ });
if (mRotationWatcherRegistered) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index ca43705..a79be6e 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -354,11 +354,13 @@
if (mPercentageStyleId != 0) { // Only set if specified as attribute
mBatteryPercentView.setTextAppearance(mPercentageStyleId);
}
+ float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
+ mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
updatePercentText();
addView(mBatteryPercentView, new LayoutParams(
LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
+ (int) Math.ceil(fontHeight)));
}
} else {
if (showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 53dc0e3..62e2369 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -25,8 +25,8 @@
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
-import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl
+import com.android.systemui.biometrics.data.repository.DisplayStateRepository
+import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl
import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -65,9 +65,10 @@
@SysUISingleton
fun fingerprintRepository(impl: FingerprintPropertyRepositoryImpl):
FingerprintPropertyRepository
+
@Binds
@SysUISingleton
- fun rearDisplayStateRepository(impl: RearDisplayStateRepositoryImpl): RearDisplayStateRepository
+ fun displayStateRepository(impl: DisplayStateRepositoryImpl): DisplayStateRepository
@Binds
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
new file mode 100644
index 0000000..7a9efcf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.DisplayListener
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+import android.os.Handler
+import android.view.DisplayInfo
+import com.android.internal.util.ArrayUtils
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.toDisplayRotation
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository for the current state of the display */
+interface DisplayStateRepository {
+ /** Provides the current rear display state. */
+ val isInRearDisplayMode: StateFlow<Boolean>
+
+ /** Provides the current display rotation */
+ val currentRotation: StateFlow<DisplayRotation>
+}
+
+@SysUISingleton
+class DisplayStateRepositoryImpl
+@Inject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ @Application val context: Context,
+ deviceStateManager: DeviceStateManager,
+ displayManager: DisplayManager,
+ @Main handler: Handler,
+ @Main mainExecutor: Executor
+) : DisplayStateRepository {
+ override val isInRearDisplayMode: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val sendRearDisplayStateUpdate = { state: Boolean ->
+ trySendWithFailureLogging(
+ state,
+ TAG,
+ "Error sending rear display state update to $state"
+ )
+ }
+
+ val callback =
+ DeviceStateManager.DeviceStateCallback { state ->
+ val isInRearDisplayMode =
+ ArrayUtils.contains(
+ context.resources.getIntArray(
+ com.android.internal.R.array.config_rearDisplayDeviceStates
+ ),
+ state
+ )
+ sendRearDisplayStateUpdate(isInRearDisplayMode)
+ }
+
+ sendRearDisplayStateUpdate(false)
+ deviceStateManager.registerCallback(mainExecutor, callback)
+ awaitClose { deviceStateManager.unregisterCallback(callback) }
+ }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ private fun getDisplayRotation(): DisplayRotation {
+ val cachedDisplayInfo = DisplayInfo()
+ context.display?.getDisplayInfo(cachedDisplayInfo)
+ return cachedDisplayInfo.rotation.toDisplayRotation()
+ }
+
+ override val currentRotation: StateFlow<DisplayRotation> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DisplayListener {
+ override fun onDisplayRemoved(displayId: Int) {}
+
+ override fun onDisplayAdded(displayId: Int) {}
+
+ override fun onDisplayChanged(displayId: Int) {
+ val rotation = getDisplayRotation()
+ trySendWithFailureLogging(
+ rotation,
+ TAG,
+ "Error sending display rotation to $rotation"
+ )
+ }
+ }
+ displayManager.registerDisplayListener(
+ callback,
+ handler,
+ EVENT_FLAG_DISPLAY_CHANGED
+ )
+ awaitClose { displayManager.unregisterDisplayListener(callback) }
+ }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = getDisplayRotation(),
+ )
+
+ companion object {
+ const val TAG = "DisplayStateRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt
deleted file mode 100644
index d17d961..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.data.repository
-
-import android.content.Context
-import android.hardware.devicestate.DeviceStateManager
-import com.android.internal.util.ArrayUtils
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
-
-/** Provide current rear display state. */
-interface RearDisplayStateRepository {
- /** Provides the current rear display state. */
- val isInRearDisplayMode: StateFlow<Boolean>
-}
-
-@SysUISingleton
-class RearDisplayStateRepositoryImpl
-@Inject
-constructor(
- @Application applicationScope: CoroutineScope,
- @Application context: Context,
- deviceStateManager: DeviceStateManager,
- @Main mainExecutor: Executor
-) : RearDisplayStateRepository {
- override val isInRearDisplayMode: StateFlow<Boolean> =
- conflatedCallbackFlow {
- val sendRearDisplayStateUpdate = { state: Boolean ->
- trySendWithFailureLogging(
- state,
- TAG,
- "Error sending rear display state update to $state"
- )
- }
-
- val callback =
- DeviceStateManager.DeviceStateCallback { state ->
- val isInRearDisplayMode =
- ArrayUtils.contains(
- context.resources.getIntArray(
- com.android.internal.R.array.config_rearDisplayDeviceStates
- ),
- state
- )
- sendRearDisplayStateUpdate(isInRearDisplayMode)
- }
-
- sendRearDisplayStateUpdate(false)
- deviceStateManager.registerCallback(mainExecutor, callback)
- awaitClose { deviceStateManager.unregisterCallback(callback) }
- }
- .stateIn(
- applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
-
- companion object {
- const val TAG = "RearDisplayStateRepositoryImpl"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index 26b6f2a..6fe79c6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -18,7 +18,8 @@
import android.content.Context
import android.content.res.Configuration
-import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.DisplayStateRepository
+import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
@@ -43,6 +44,9 @@
/** Whether the device is currently folded. */
val isFolded: Flow<Boolean>
+ /** Current rotation of the display */
+ val currentRotation: StateFlow<DisplayRotation>
+
/** Called on configuration changes, used to keep the display state in sync */
fun onConfigurationChanged(newConfig: Configuration)
}
@@ -54,7 +58,7 @@
@Application applicationScope: CoroutineScope,
@Application context: Context,
@Main mainExecutor: Executor,
- rearDisplayStateRepository: RearDisplayStateRepository,
+ displayStateRepository: DisplayStateRepository,
) : DisplayStateInteractor {
private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
@@ -90,7 +94,10 @@
)
override val isInRearDisplayMode: StateFlow<Boolean> =
- rearDisplayStateRepository.isInRearDisplayMode
+ displayStateRepository.isInRearDisplayMode
+
+ override val currentRotation: StateFlow<DisplayRotation> =
+ displayStateRepository.currentRotation
override fun onConfigurationChanged(newConfig: Configuration) {
screenSizeFoldProvider.onConfigurationChange(newConfig)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
new file mode 100644
index 0000000..10a3e91
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
@@ -0,0 +1,21 @@
+package com.android.systemui.biometrics.shared.model
+
+import android.view.Surface
+
+/** Shadows [Surface.Rotation] for kotlin use within SysUI. */
+enum class DisplayRotation {
+ ROTATION_0,
+ ROTATION_90,
+ ROTATION_180,
+ ROTATION_270,
+}
+
+/** Converts [Surface.Rotation] to corresponding [DisplayRotation] */
+fun Int.toDisplayRotation(): DisplayRotation =
+ when (this) {
+ Surface.ROTATION_0 -> DisplayRotation.ROTATION_0
+ Surface.ROTATION_90 -> DisplayRotation.ROTATION_90
+ Surface.ROTATION_180 -> DisplayRotation.ROTATION_180
+ Surface.ROTATION_270 -> DisplayRotation.ROTATION_270
+ else -> throw IllegalArgumentException("Invalid DisplayRotation value: $this")
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index b1439fd..ce31b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -261,6 +261,18 @@
}
}
+ // set padding
+ launch {
+ viewModel.promptPadding.collect { promptPadding ->
+ view.setPadding(
+ promptPadding.left,
+ promptPadding.top,
+ promptPadding.right,
+ promptPadding.bottom
+ )
+ }
+ }
+
// configure & hide/disable buttons
launch {
viewModel.credentialKind
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index be08932..25634aa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -15,16 +15,21 @@
*/
package com.android.systemui.biometrics.ui.viewmodel
+import android.content.Context
+import android.graphics.Rect
import android.hardware.biometrics.BiometricPrompt
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import com.android.systemui.biometrics.AuthBiometricView
+import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.statusbar.VibratorHelper
@@ -49,6 +54,7 @@
private val displayStateInteractor: DisplayStateInteractor,
private val promptSelectorInteractor: PromptSelectorInteractor,
private val vibrator: VibratorHelper,
+ @Application context: Context,
private val featureFlags: FeatureFlags,
) {
/** Models UI of [BiometricPromptLayout.iconView] */
@@ -135,6 +141,23 @@
!isOverlayTouched && size.isNotSmall
}
+ /** Padding for prompt UI elements */
+ val promptPadding: Flow<Rect> =
+ combine(size, displayStateInteractor.currentRotation) { size, rotation ->
+ if (size != PromptSize.LARGE) {
+ val navBarInsets = Utils.getNavbarInsets(context)
+ if (rotation == DisplayRotation.ROTATION_90) {
+ Rect(0, 0, navBarInsets.right, 0)
+ } else if (rotation == DisplayRotation.ROTATION_270) {
+ Rect(navBarInsets.left, 0, 0, 0)
+ } else {
+ Rect(0, 0, 0, navBarInsets.bottom)
+ }
+ } else {
+ Rect(0, 0, 0, 0)
+ }
+ }
+
/** Title for the prompt. */
val title: Flow<String> =
promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 5d732fb..cbb7e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.appselector.view
import android.app.ActivityOptions
+import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
import android.app.IActivityTaskManager
import android.graphics.Rect
import android.os.Binder
@@ -129,6 +130,9 @@
view.width,
view.height
)
+ activityOptions.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
activityOptions.launchCookie = launchCookie
activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 146b5f5..79e7b71 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -359,6 +359,7 @@
public void setBackgroundExecutor(Executor bgExecutor) {
mBgExecutor = bgExecutor;
+ mRotationButtonController.setBgExecutor(bgExecutor);
}
public void setDisplayTracker(DisplayTracker displayTracker) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 83a040c..1966033 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -40,6 +40,7 @@
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.leak.RotationUtils;
@@ -51,7 +52,7 @@
private final StatusBarContentInsetsProvider mContentInsetsProvider;
private DarkReceiver mBattery;
- private DarkReceiver mClock;
+ private Clock mClock;
private int mRotationOrientation = -1;
@Nullable
private View mCutoutSpace;
@@ -123,6 +124,10 @@
}
}
+ void onDensityOrFontScaleChanged() {
+ mClock.onDensityOrFontScaleChanged();
+ }
+
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if (updateDisplayParameters()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 4de669c..85a75ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -71,12 +71,20 @@
override fun onConfigChanged(newConfig: Configuration?) {
mView.updateResources()
}
+
+ override fun onDensityOrFontScaleChanged() {
+ mView.onDensityOrFontScaleChanged()
+ }
}
override fun onViewAttached() {
statusContainer = mView.requireViewById(R.id.system_icons)
statusContainer.setOnHoverListener(
statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
+
+ progressProvider?.setReadyToHandleTransition(true)
+ configurationController.addCallback(configurationListener)
+
if (moveFromCenterAnimationController == null) return
val statusBarLeftSide: View =
@@ -103,9 +111,6 @@
moveFromCenterAnimationController.onStatusBarWidthChanged()
}
}
-
- progressProvider?.setReadyToHandleTransition(true)
- configurationController.addCallback(configurationListener)
}
override fun onViewDetached() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 4b2fb43..6483b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -19,7 +19,6 @@
import android.content.res.ColorStateList
import android.view.View
import android.view.View.GONE
-import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView
@@ -33,12 +32,11 @@
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -83,20 +81,11 @@
launch {
visibilityState.collect { state ->
- when (state) {
- STATE_ICON -> {
- mobileGroupView.visibility = VISIBLE
- dotView.visibility = GONE
- }
- STATE_DOT -> {
- mobileGroupView.visibility = INVISIBLE
- dotView.visibility = VISIBLE
- }
- STATE_HIDDEN -> {
- mobileGroupView.visibility = INVISIBLE
- dotView.visibility = INVISIBLE
- }
- }
+ ModernStatusBarViewVisibilityHelper.setVisibilityState(
+ state,
+ mobileGroupView,
+ dotView,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewVisibilityHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewVisibilityHelper.kt
new file mode 100644
index 0000000..6668cbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewVisibilityHelper.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.binder
+
+import android.view.View
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
+import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
+
+/**
+ * The helper to update the groupView and dotView visibility based on given visibility state, only
+ * used for [MobileIconBinder] and [WifiViewBinder] now.
+ */
+class ModernStatusBarViewVisibilityHelper {
+ companion object {
+
+ fun setVisibilityState(
+ @StatusBarIconView.VisibleState state: Int,
+ groupView: View,
+ dotView: View,
+ ) {
+ when (state) {
+ StatusBarIconView.STATE_ICON -> {
+ groupView.visibility = View.VISIBLE
+ dotView.visibility = View.GONE
+ }
+ StatusBarIconView.STATE_DOT -> {
+ groupView.visibility = View.INVISIBLE
+ dotView.visibility = View.VISIBLE
+ }
+ StatusBarIconView.STATE_HIDDEN -> {
+ groupView.visibility = View.INVISIBLE
+ dotView.visibility = View.INVISIBLE
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 3082a66..e593575 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -27,10 +27,9 @@
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
@@ -83,8 +82,18 @@
launch {
visibilityState.collect { visibilityState ->
- groupView.isVisible = visibilityState == STATE_ICON
- dotView.isVisible = visibilityState == STATE_DOT
+ // for b/296864006, we can not hide all the child views if visibilityState
+ // is STATE_HIDDEN. Because hiding all child views would cause the
+ // getWidth() of this view return 0, and that would cause the translation
+ // calculation fails in StatusIconContainer. Therefore, like class
+ // MobileIconBinder, instead of set the child views visibility to View.GONE,
+ // we set their visibility to View.INVISIBLE to make them invisible but
+ // keep the width.
+ ModernStatusBarViewVisibilityHelper.setVisibilityState(
+ visibilityState,
+ groupView,
+ dotView,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index f994372..b7ae233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -37,9 +37,11 @@
import android.text.style.CharacterStyle;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.TextView;
import com.android.settingslib.Utils;
@@ -143,6 +145,8 @@
}
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
mUserTracker = Dependency.get(UserTracker.class);
+
+ setIncludeFontPadding(false);
}
@Override
@@ -389,6 +393,15 @@
mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_clock_end_padding),
0);
+
+ float fontHeight = getPaint().getFontMetricsInt(null);
+ setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
+
+ ViewGroup.LayoutParams lp = getLayoutParams();
+ if (lp != null) {
+ lp.height = (int) Math.ceil(fontHeight);
+ setLayoutParams(lp);
+ }
}
private void updateShowSeconds() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 9584d88..bddc4c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -41,7 +41,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
@@ -110,7 +110,7 @@
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val biometricPromptRepository = FakePromptRepository()
private val fingerprintRepository = FakeFingerprintPropertyRepository()
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ private val displayStateRepository = FakeDisplayStateRepository()
private val credentialInteractor = FakeCredentialInteractor()
private val bpCredentialInteractor = PromptCredentialInteractor(
Dispatchers.Main.immediate,
@@ -129,7 +129,7 @@
testScope.backgroundScope,
mContext,
fakeExecutor,
- rearDisplayStateRepository
+ displayStateRepository
)
@@ -531,6 +531,7 @@
displayStateInteractor,
promptSelectorInteractor,
vibrator,
+ context,
featureFlags
),
{ credentialViewModel },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 994db46..c71ef47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -56,7 +56,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
-import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -120,7 +120,7 @@
private lateinit var displayStateInteractor: DisplayStateInteractor
private val executor = FakeExecutor(FakeSystemClock())
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ private val displayStateRepository = FakeDisplayStateRepository()
private val testScope = TestScope(StandardTestDispatcher())
private lateinit var overlayController: ISidefpsController
@@ -157,7 +157,7 @@
testScope.backgroundScope,
context,
executor,
- rearDisplayStateRepository
+ displayStateRepository
)
context.addMockSystemService(DisplayManager::class.java, displayManager)
@@ -268,7 +268,7 @@
TestCoroutineScope(),
dumpManager
)
- rearDisplayStateRepository.setIsInRearDisplayMode(inRearDisplayMode)
+ displayStateRepository.setIsInRearDisplayMode(inRearDisplayMode)
overlayController =
ArgumentCaptor.forClass(ISidefpsController::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
new file mode 100644
index 0000000..c9c46cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.DisplayStateRepository
+import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
+private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DisplayStateRepositoryTest : SysuiTestCase() {
+ @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
+ @Mock private lateinit var deviceStateManager: DeviceStateManager
+ @Mock private lateinit var displayManager: DisplayManager
+ @Mock private lateinit var handler: Handler
+ @Mock private lateinit var display: Display
+ private lateinit var underTest: DisplayStateRepository
+
+ private val testScope = TestScope(StandardTestDispatcher())
+ private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+ @Captor
+ private lateinit var displayListenerCaptor: ArgumentCaptor<DisplayManager.DisplayListener>
+
+ @Before
+ fun setUp() {
+ val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.array.config_rearDisplayDeviceStates,
+ rearDisplayDeviceStates
+ )
+
+ mContext = spy(mContext)
+ whenever(mContext.display).thenReturn(display)
+
+ underTest =
+ DisplayStateRepositoryImpl(
+ testScope.backgroundScope,
+ mContext,
+ deviceStateManager,
+ displayManager,
+ handler,
+ fakeExecutor
+ )
+ }
+
+ @Test
+ fun updatesIsInRearDisplayMode_whenRearDisplayStateChanges() =
+ testScope.runTest {
+ val isInRearDisplayMode by collectLastValue(underTest.isInRearDisplayMode)
+ runCurrent()
+
+ val callback = deviceStateManager.captureCallback()
+
+ callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
+ assertThat(isInRearDisplayMode).isFalse()
+
+ callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
+ assertThat(isInRearDisplayMode).isTrue()
+ }
+
+ @Test
+ fun updatesCurrentRotation_whenDisplayStateChanges() =
+ testScope.runTest {
+ val currentRotation by collectLastValue(underTest.currentRotation)
+ runCurrent()
+
+ verify(displayManager)
+ .registerDisplayListener(
+ displayListenerCaptor.capture(),
+ same(handler),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
+ )
+
+ whenever(display.getDisplayInfo(any())).then {
+ val info = it.getArgument<DisplayInfo>(0)
+ info.rotation = Surface.ROTATION_90
+ return@then true
+ }
+ displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_90)
+ assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90)
+
+ whenever(display.getDisplayInfo(any())).then {
+ val info = it.getArgument<DisplayInfo>(0)
+ info.rotation = Surface.ROTATION_180
+ return@then true
+ }
+ displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+ assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
+ }
+}
+
+private fun DeviceStateManager.captureCallback() =
+ withArgCaptor<DeviceStateManager.DeviceStateCallback> {
+ verify(this@captureCallback).registerCallback(any(), capture())
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt
deleted file mode 100644
index dfe8d36..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import android.hardware.devicestate.DeviceStateManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-
-private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
-private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class RearDisplayStateRepositoryTest : SysuiTestCase() {
- @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var deviceStateManager: DeviceStateManager
- private lateinit var underTest: RearDisplayStateRepository
-
- private val testScope = TestScope(StandardTestDispatcher())
- private val fakeExecutor = FakeExecutor(FakeSystemClock())
-
- @Captor
- private lateinit var callbackCaptor: ArgumentCaptor<DeviceStateManager.DeviceStateCallback>
-
- @Before
- fun setUp() {
- val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.array.config_rearDisplayDeviceStates,
- rearDisplayDeviceStates
- )
-
- underTest =
- RearDisplayStateRepositoryImpl(
- testScope.backgroundScope,
- mContext,
- deviceStateManager,
- fakeExecutor
- )
- }
-
- @Test
- fun updatesIsInRearDisplayMode_whenRearDisplayStateChanges() =
- testScope.runTest {
- val isInRearDisplayMode = collectLastValue(underTest.isInRearDisplayMode)
- runCurrent()
-
- val callback = deviceStateManager.captureCallback()
-
- callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
- assertThat(isInRearDisplayMode()).isFalse()
-
- callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
- assertThat(isInRearDisplayMode()).isTrue()
- }
-}
-
-private fun DeviceStateManager.captureCallback() =
- withArgCaptor<DeviceStateManager.DeviceStateCallback> {
- verify(this@captureCallback).registerCallback(any(), capture())
- }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
index 2217c5c..0f84d9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
@@ -2,7 +2,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
+import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.unfold.updates.FoldProvider
@@ -34,7 +35,7 @@
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope(StandardTestDispatcher())
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ private val displayStateRepository = FakeDisplayStateRepository()
@Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider
private lateinit var interactor: DisplayStateInteractorImpl
@@ -46,7 +47,7 @@
testScope.backgroundScope,
mContext,
fakeExecutor,
- rearDisplayStateRepository
+ displayStateRepository
)
interactor.setScreenSizeFoldProvider(screenSizeFoldProvider)
}
@@ -56,14 +57,26 @@
testScope.runTest {
val isInRearDisplayMode = collectLastValue(interactor.isInRearDisplayMode)
- rearDisplayStateRepository.setIsInRearDisplayMode(false)
+ displayStateRepository.setIsInRearDisplayMode(false)
assertThat(isInRearDisplayMode()).isFalse()
- rearDisplayStateRepository.setIsInRearDisplayMode(true)
+ displayStateRepository.setIsInRearDisplayMode(true)
assertThat(isInRearDisplayMode()).isTrue()
}
@Test
+ fun currentRotationChanges() =
+ testScope.runTest {
+ val currentRotation = collectLastValue(interactor.currentRotation)
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ assertThat(currentRotation()).isEqualTo(DisplayRotation.ROTATION_180)
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+ assertThat(currentRotation()).isEqualTo(DisplayRotation.ROTATION_90)
+ }
+
+ @Test
fun isFoldedChanges() =
testScope.runTest {
val isFolded = collectLastValue(interactor.isFolded)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
index 7697c09..0d61e30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
@@ -4,9 +4,9 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -41,7 +41,7 @@
private val fingerprintRepository = FakeFingerprintPropertyRepository()
private val promptRepository = FakePromptRepository()
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ private val displayStateRepository = FakeDisplayStateRepository()
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -59,7 +59,7 @@
testScope.backgroundScope,
mContext,
fakeExecutor,
- rearDisplayStateRepository
+ displayStateRepository
)
viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 0ed46da..c53172d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -25,9 +25,9 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthBiometricView
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
@@ -79,14 +79,14 @@
private val testScope = TestScope()
private val fingerprintRepository = FakeFingerprintPropertyRepository()
private val promptRepository = FakePromptRepository()
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ private val displayStateRepository = FakeDisplayStateRepository()
private val displayStateInteractor =
DisplayStateInteractorImpl(
testScope.backgroundScope,
mContext,
fakeExecutor,
- rearDisplayStateRepository
+ displayStateRepository
)
private lateinit var selector: PromptSelectorInteractor
@@ -99,7 +99,8 @@
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
selector.resetPrompt()
- viewModel = PromptViewModel(displayStateInteractor, selector, vibrator, featureFlags)
+ viewModel =
+ PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 0b31523..af0d6b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -103,6 +103,21 @@
}
@Test
+ fun onViewAttachedAndDrawn_startListeningConfigurationControllerCallback() {
+ val view = createViewMock()
+ val argumentCaptor = ArgumentCaptor.forClass(
+ ConfigurationController.ConfigurationListener::class.java)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
+
+ verify(configurationController).addCallback(argumentCaptor.capture())
+ argumentCaptor.value.onDensityOrFontScaleChanged()
+
+ verify(view).onDensityOrFontScaleChanged()
+ }
+
+ @Test
fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() {
whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS))
.thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index a0d4d13..bbf048d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -22,6 +22,7 @@
import android.testing.TestableLooper.RunWithLooper
import android.testing.ViewUtils
import android.view.View
+import android.view.ViewGroup
import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -138,7 +139,7 @@
ViewUtils.attachView(view)
testableLooper.processAllMessages()
- assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
+ assertThat(view.getIconGroupView().visibility).isEqualTo(View.INVISIBLE)
assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE)
ViewUtils.detachView(view)
@@ -153,8 +154,36 @@
ViewUtils.attachView(view)
testableLooper.processAllMessages()
- assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
- assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+ assertThat(view.getIconGroupView().visibility).isEqualTo(View.INVISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.INVISIBLE)
+
+ ViewUtils.detachView(view)
+ }
+
+ /* Regression test for b/296864006. When STATE_HIDDEN we need to ensure the wifi view width
+ * would not break the StatusIconContainer translation calculation. */
+ @Test
+ fun setVisibleState_hidden_keepWidth() {
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+ // get the view width when it's in visible state
+ ViewUtils.attachView(view)
+ val lp = view.layoutParams
+ lp.width = ViewGroup.LayoutParams.WRAP_CONTENT
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ view.layoutParams = lp
+ testableLooper.processAllMessages()
+ val currentWidth = view.width
+
+ view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+ testableLooper.processAllMessages()
+
+ // the view width when STATE_HIDDEN should be at least the width when STATE_ICON. Because
+ // when STATE_HIDDEN the invisible dot view width might be larger than group view width,
+ // then the wifi view width would be enlarged.
+ assertThat(view.width).isAtLeast(currentWidth)
ViewUtils.detachView(view)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
similarity index 71%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
index fd91391..60291ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
@@ -17,15 +17,23 @@
package com.android.systemui.biometrics.data.repository
+import com.android.systemui.biometrics.shared.model.DisplayRotation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-class FakeRearDisplayStateRepository : RearDisplayStateRepository {
+class FakeDisplayStateRepository : DisplayStateRepository {
private val _isInRearDisplayMode = MutableStateFlow<Boolean>(false)
override val isInRearDisplayMode: StateFlow<Boolean> = _isInRearDisplayMode.asStateFlow()
+ private val _currentRotation = MutableStateFlow<DisplayRotation>(DisplayRotation.ROTATION_0)
+ override val currentRotation: StateFlow<DisplayRotation> = _currentRotation.asStateFlow()
+
fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) {
_isInRearDisplayMode.value = isInRearDisplayMode
}
+
+ fun setCurrentRotation(currentRotation: DisplayRotation) {
+ _currentRotation.value = currentRotation
+ }
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 9d6283b8..2511b50 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -64,6 +64,7 @@
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
+import android.companion.datatransfer.PermissionSyncRequest;
import android.companion.utils.FeatureUtils;
import android.content.ComponentName;
import android.content.Context;
@@ -794,6 +795,24 @@
}
@Override
+ public void enablePermissionsSync(int associationId) {
+ getAssociationWithCallerChecks(associationId);
+ mSystemDataTransferProcessor.enablePermissionsSync(associationId);
+ }
+
+ @Override
+ public void disablePermissionsSync(int associationId) {
+ getAssociationWithCallerChecks(associationId);
+ mSystemDataTransferProcessor.disablePermissionsSync(associationId);
+ }
+
+ @Override
+ public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+ getAssociationWithCallerChecks(associationId);
+ return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ }
+
+ @Override
public void enableSecureTransport(boolean enabled) {
mTransportManager.enableSecureTransport(enabled);
}
@@ -934,7 +953,7 @@
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, mAssociationStore,
- mDevicePresenceMonitor, mTransportManager, mSystemDataTransferRequestStore,
+ mDevicePresenceMonitor, mTransportManager, mSystemDataTransferProcessor,
mAssociationRequestsProcessor)
.exec(this, in, out, err, args, callback, resultReceiver);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 04fbab4..d56436b1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -25,7 +25,7 @@
import android.os.ShellCommand;
import android.util.proto.ProtoOutputStream;
-import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
@@ -43,20 +43,20 @@
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final CompanionTransportManager mTransportManager;
- private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+ private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStoreImpl associationStore,
CompanionDevicePresenceMonitor devicePresenceMonitor,
CompanionTransportManager transportManager,
- SystemDataTransferRequestStore systemDataTransferRequestStore,
+ SystemDataTransferProcessor systemDataTransferProcessor,
AssociationRequestsProcessor associationRequestsProcessor) {
mService = service;
mAssociationStore = associationStore;
mDevicePresenceMonitor = devicePresenceMonitor;
mTransportManager = transportManager;
- mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+ mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
}
@@ -265,16 +265,47 @@
break;
}
- case "allow-permission-sync": {
- int userId = getNextIntArgRequired();
+ case "get-perm-sync-state": {
associationId = getNextIntArgRequired();
- boolean enabled = getNextBooleanArgRequired();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserId(userId);
- request.setUserConsented(enabled);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
+ PermissionSyncRequest request =
+ mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ out.println((request == null ? "null" : request.isUserConsented()));
+ break;
}
- break;
+
+ case "remove-perm-sync-state": {
+ associationId = getNextIntArgRequired();
+ PermissionSyncRequest request =
+ mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ out.print((request == null ? "null" : request.isUserConsented()));
+ mSystemDataTransferProcessor.removePermissionSyncRequest(associationId);
+ request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ // should print " -> null"
+ out.println(" -> " + (request == null ? "null" : request.isUserConsented()));
+ break;
+ }
+
+ case "enable-perm-sync": {
+ associationId = getNextIntArgRequired();
+ PermissionSyncRequest request =
+ mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ out.print((request == null ? "null" : request.isUserConsented()));
+ mSystemDataTransferProcessor.enablePermissionsSync(associationId);
+ request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ out.println(" -> " + request.isUserConsented()); // should print " -> true"
+ break;
+ }
+
+ case "disable-perm-sync": {
+ associationId = getNextIntArgRequired();
+ PermissionSyncRequest request =
+ mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ out.print((request == null ? "null" : request.isUserConsented()));
+ mSystemDataTransferProcessor.disablePermissionsSync(associationId);
+ request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ out.println(" -> " + request.isUserConsented()); // should print " -> false"
+ break;
+ }
default:
return handleDefaultCommands(cmd);
@@ -346,5 +377,14 @@
pw.println(" create-emulated-transport <ASSOCIATION_ID>");
pw.println(" Create an EmulatedTransport for testing purposes only");
+
+ pw.println(" enable-perm-sync <ASSOCIATION_ID>");
+ pw.println(" Enable perm sync for the association.");
+ pw.println(" disable-perm-sync <ASSOCIATION_ID>");
+ pw.println(" Disable perm sync for the association.");
+ pw.println(" get-perm-sync-state <ASSOCIATION_ID>");
+ pw.println(" Get perm sync state for the association.");
+ pw.println(" remove-perm-sync-state <ASSOCIATION_ID>");
+ pw.println(" Remove perm sync state for the association.");
}
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 800a3d9..aeb273b 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -138,6 +138,13 @@
@UserIdInt int userId, int associationId) {
if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null.");
+ // Auto enable perm sync for the allowlisted packages, but don't override user decision
+ PermissionSyncRequest request = getPermissionSyncRequest(associationId);
+ if (request == null) {
+ PermissionSyncRequest newRequest = new PermissionSyncRequest(associationId);
+ newRequest.setUserConsented(true);
+ mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
+ }
return null;
}
@@ -184,29 +191,17 @@
final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
// Check if the request has been consented by the user.
- if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
- Slog.i(LOG_TAG, "Skip user consent check due to the same OEM package.");
- } else {
- List<SystemDataTransferRequest> storedRequests =
- mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
- associationId);
- boolean hasConsented = false;
- for (SystemDataTransferRequest storedRequest : storedRequests) {
- if (storedRequest instanceof PermissionSyncRequest
- && storedRequest.isUserConsented()) {
- hasConsented = true;
- break;
- }
+ PermissionSyncRequest request = getPermissionSyncRequest(associationId);
+ if (request == null || !request.isUserConsented()) {
+ String message =
+ "User " + userId + " hasn't consented permission sync for associationId ["
+ + associationId + ".";
+ Slog.e(LOG_TAG, message);
+ try {
+ callback.onError(message);
+ } catch (RemoteException ignored) {
}
- if (!hasConsented) {
- String message = "User " + userId + " hasn't consented permission sync.";
- Slog.e(LOG_TAG, message);
- try {
- callback.onError(message);
- } catch (RemoteException ignored) {
- }
- return;
- }
+ return;
}
// Start permission sync
@@ -224,6 +219,66 @@
}
}
+ /**
+ * Enable perm sync for the association
+ */
+ public void enablePermissionsSync(int associationId) {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(true);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
+ }
+
+ /**
+ * Disable perm sync for the association
+ */
+ public void disablePermissionsSync(int associationId) {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(false);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
+ }
+
+ /**
+ * Get perm sync request for the association.
+ */
+ @Nullable
+ public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ List<SystemDataTransferRequest> requests =
+ mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
+ associationId);
+ for (SystemDataTransferRequest request : requests) {
+ if (request instanceof PermissionSyncRequest) {
+ return (PermissionSyncRequest) request;
+ }
+ }
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
+ }
+
+ /**
+ * Remove perm sync request for the association.
+ */
+ public void removePermissionSyncRequest(int associationId) {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
+ }
+
private void onReceivePermissionRestore(byte[] message) {
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 720cefa..9f489e8 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -104,7 +104,7 @@
}
@NonNull
- List<SystemDataTransferRequest> readRequestsByAssociationId(@UserIdInt int userId,
+ public List<SystemDataTransferRequest> readRequestsByAssociationId(@UserIdInt int userId,
int associationId) {
List<SystemDataTransferRequest> cachedRequests;
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/SmartStorageMaintIdler.java b/services/core/java/com/android/server/SmartStorageMaintIdler.java
index 8996926..44f1e76 100644
--- a/services/core/java/com/android/server/SmartStorageMaintIdler.java
+++ b/services/core/java/com/android/server/SmartStorageMaintIdler.java
@@ -25,6 +25,7 @@
import android.util.Slog;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
public class SmartStorageMaintIdler extends JobService {
private static final String TAG = "SmartStorageMaintIdler";
@@ -34,15 +35,15 @@
private static final int SMART_MAINT_JOB_ID = 2808;
- private boolean mStarted;
+ private final AtomicBoolean mStarted = new AtomicBoolean(false);
private JobParameters mJobParams;
private final Runnable mFinishCallback = new Runnable() {
@Override
public void run() {
Slog.i(TAG, "Got smart storage maintenance service completion callback");
- if (mStarted) {
+ if (mStarted.get()) {
jobFinished(mJobParams, false);
- mStarted = false;
+ mStarted.set(false);
}
// ... and try again in a next period
scheduleSmartIdlePass(SmartStorageMaintIdler.this,
@@ -52,18 +53,26 @@
@Override
public boolean onStartJob(JobParameters params) {
- mJobParams = params;
- StorageManagerService ms = StorageManagerService.sSelf;
- if (ms != null) {
- mStarted = true;
- ms.runSmartIdleMaint(mFinishCallback);
+ final StorageManagerService ms = StorageManagerService.sSelf;
+ if (mStarted.compareAndSet(false, true)) {
+ new Thread() {
+ public void run() {
+ mJobParams = params;
+ if (ms != null) {
+ ms.runSmartIdleMaint(mFinishCallback);
+ } else {
+ mStarted.set(false);
+ }
+ }
+ }.start();
+ return ms != null;
}
- return ms != null;
+ return false;
}
@Override
public boolean onStopJob(JobParameters params) {
- mStarted = false;
+ mStarted.set(false);
return false;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index d47573d..2606757 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2771,7 +2771,7 @@
return true;
}
- void runSmartIdleMaint(Runnable callback) {
+ synchronized void runSmartIdleMaint(Runnable callback) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index adea13f..24c7b87 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -323,10 +323,15 @@
static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) {
ProgramIdentifier hwId = new ProgramIdentifier();
hwId.type = id.getType();
- if (hwId.type == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+ if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
hwId.type = IdentifierType.DAB_SID_EXT;
}
- hwId.value = id.getValue();
+ long value = id.getValue();
+ if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) {
+ hwId.value = (value & 0xFFFF) | ((value >>> 16) << 32);
+ } else {
+ hwId.value = value;
+ }
return hwId;
}
@@ -609,6 +614,9 @@
|| isNewIdentifierInU(info.getPhysicallyTunedTo())) {
return false;
}
+ if (info.getRelatedContent() == null) {
+ return true;
+ }
Iterator<ProgramSelector.Identifier> relatedContentIt = info.getRelatedContent().iterator();
while (relatedContentIt.hasNext()) {
if (isNewIdentifierInU(relatedContentIt.next())) {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 9954a90..802a7f2 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -975,6 +975,9 @@
throw new SecurityException("Media projections require a foreground service"
+ " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION");
}
+
+ mCallback = callback;
+ registerCallback(mCallback);
try {
mToken = callback.asBinder();
mDeathEater = () -> {
@@ -1019,11 +1022,6 @@
}
}
startProjectionLocked(this);
-
- // Register new callbacks after stop has been dispatched to previous session.
- mCallback = callback;
- registerCallback(mCallback);
-
// Mark this token as used when the app gets the MediaProjection instance.
mCountStarts++;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5947b75..6601467 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -999,12 +999,11 @@
return;
}
- if (!mWallpaper.wallpaperUpdating
- && mWallpaper.userId == mCurrentUserId) {
+ if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
+ ", reverting to built-in wallpaper!");
- clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId,
- null);
+ int which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM;
+ clearWallpaperLocked(true, which, mWallpaper.userId, null);
}
}
};
@@ -1184,7 +1183,7 @@
} else {
// Timeout
Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+ clearWallpaperLocked(true, mWallpaper.mWhich, mWallpaper.userId, null);
final String flattened = wpService.flattenToString();
EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
flattened.substring(0, Math.min(flattened.length(),
@@ -1213,7 +1212,7 @@
&& mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
> SystemClock.uptimeMillis()) {
Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+ clearWallpaperLocked(true, mWallpaper.mWhich, mWallpaper.userId, null);
} else {
mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
tryToRebind();
@@ -1427,8 +1426,7 @@
if (mCurrentUserId != getChangingUserId()) {
return;
}
- WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
- if (wallpaper != null) {
+ for (WallpaperData wallpaper: getWallpapers()) {
final ComponentName wpService = wallpaper.wallpaperComponent;
if (wpService != null && wpService.getPackageName().equals(packageName)) {
if (DEBUG_LIVE) {
@@ -1440,7 +1438,9 @@
wallpaper, null)) {
Slog.w(TAG, "Wallpaper " + wpService
+ " no longer available; reverting to default");
- clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null);
+ int which = mIsLockscreenLiveWallpaperEnabled
+ ? wallpaper.mWhich : FLAG_SYSTEM;
+ clearWallpaperLocked(false, which, wallpaper.userId, null);
}
}
}
@@ -1453,13 +1453,11 @@
if (mCurrentUserId != getChangingUserId()) {
return;
}
- WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
- if (wallpaper != null) {
- if (wallpaper.wallpaperComponent == null
- || !wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
- return;
+ for (WallpaperData wallpaper: getWallpapers()) {
+ if (wallpaper.wallpaperComponent != null
+ && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
+ doPackagesChangedLocked(true, wallpaper);
}
- doPackagesChangedLocked(true, wallpaper);
}
}
}
@@ -1470,8 +1468,7 @@
if (mCurrentUserId != getChangingUserId()) {
return;
}
- WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
- if (wallpaper != null) {
+ for (WallpaperData wallpaper: getWallpapers()) {
if (wallpaper.wallpaperComponent != null
&& wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
if (DEBUG_LIVE) {
@@ -1495,8 +1492,7 @@
if (mCurrentUserId != getChangingUserId()) {
return false;
}
- WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
- if (wallpaper != null) {
+ for (WallpaperData wallpaper: getWallpapers()) {
boolean res = doPackagesChangedLocked(doit, wallpaper);
changed |= res;
}
@@ -1510,8 +1506,7 @@
if (mCurrentUserId != getChangingUserId()) {
return;
}
- WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
- if (wallpaper != null) {
+ for (WallpaperData wallpaper: getWallpapers()) {
doPackagesChangedLocked(true, wallpaper);
}
}
@@ -1519,6 +1514,7 @@
boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
boolean changed = false;
+ int which = mIsLockscreenLiveWallpaperEnabled ? wallpaper.mWhich : FLAG_SYSTEM;
if (wallpaper.wallpaperComponent != null) {
int change = isPackageDisappearing(wallpaper.wallpaperComponent
.getPackageName());
@@ -1528,7 +1524,7 @@
if (doit) {
Slog.w(TAG, "Wallpaper uninstalled, removing: "
+ wallpaper.wallpaperComponent);
- clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null);
+ clearWallpaperLocked(false, which, wallpaper.userId, null);
}
}
}
@@ -1549,7 +1545,7 @@
} catch (NameNotFoundException e) {
Slog.w(TAG, "Wallpaper component gone, removing: "
+ wallpaper.wallpaperComponent);
- clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null);
+ clearWallpaperLocked(false, which, wallpaper.userId, null);
}
}
if (wallpaper.nextWallpaperComponent != null
@@ -1664,7 +1660,8 @@
if (DEBUG) {
Slog.i(TAG, "Unable to regenerate crop; resetting");
}
- clearWallpaperLocked(false, FLAG_SYSTEM, UserHandle.USER_SYSTEM, null);
+ int which = isLockscreenLiveWallpaperEnabled() ? wallpaper.mWhich : FLAG_SYSTEM;
+ clearWallpaperLocked(false, which, UserHandle.USER_SYSTEM, null);
}
} else {
if (DEBUG) {
@@ -2786,6 +2783,18 @@
: new WallpaperData[0];
}
+ // TODO(b/266818039) remove
+ private WallpaperData[] getWallpapers() {
+ WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
+ WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
+ boolean systemValid = systemWallpaper != null;
+ boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled();
+ return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
+ : systemValid ? new WallpaperData[]{systemWallpaper}
+ : lockValid ? new WallpaperData[]{lockWallpaper}
+ : new WallpaperData[0];
+ }
+
private IWallpaperEngine getEngine(int which, int userId, int displayId) {
WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
if (wallpaperData == null) return null;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index d8c684f..4979274 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -96,6 +96,9 @@
private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
"enable_credential_manager";
+ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+ "enable_credential_description_api";
+
private final Context mContext;
/** Cache of system service list per user id. */
@@ -321,7 +324,14 @@
}
public static boolean isCredentialDescriptionApiEnabled() {
- return true;
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
+ false);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -954,7 +964,7 @@
Slog.i(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
if (!isCredentialDescriptionApiEnabled()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Feature not supported");
}
enforceCallingPackage(callingPackage, Binder.getCallingUid());
@@ -974,7 +984,7 @@
if (!isCredentialDescriptionApiEnabled()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Feature not supported");
}
enforceCallingPackage(callingPackage, Binder.getCallingUid());
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index b0fd606..ddd1221 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -202,24 +202,6 @@
}
@Test
- public void testCreateProjection_priorProjectionGrant() throws NameNotFoundException {
- // Create a first projection.
- MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
- FakeIMediaProjectionCallback callback1 = new FakeIMediaProjectionCallback();
- projection.start(callback1);
-
- // Create a second projection.
- MediaProjectionManagerService.MediaProjection secondProjection =
- startProjectionPreconditions();
- FakeIMediaProjectionCallback callback2 = new FakeIMediaProjectionCallback();
- secondProjection.start(callback2);
-
- // Check that the second projection's callback hasn't been stopped.
- assertThat(callback1.mStopped).isTrue();
- assertThat(callback2.mStopped).isFalse();
- }
-
- @Test
public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
throws NameNotFoundException {
// Create a first projection.
@@ -803,11 +785,8 @@
}
private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
- boolean mStopped = false;
-
@Override
public void onStop() throws RemoteException {
- mStopped = true;
}
@Override
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
index 9015563..2411498 100644
--- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
@@ -63,6 +63,12 @@
public void createSession(String engine,
ITextToSpeechSessionCallback sessionCallback) {
synchronized (mLock) {
+ if (engine == null) {
+ runSessionCallbackMethod(
+ () -> sessionCallback.onError("Engine cannot be null"));
+ return;
+ }
+
TextToSpeechManagerPerUserService perUserService = getServiceForUserLocked(
UserHandle.getCallingUserId());
if (perUserService != null) {
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index d41c019..bc41829 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -23,9 +23,11 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback;
@@ -35,6 +37,7 @@
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
@@ -67,7 +70,7 @@
@SystemApi
public class SharedConnectivityManager {
private static final String TAG = SharedConnectivityManager.class.getSimpleName();
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final class SharedConnectivityCallbackProxy extends
ISharedConnectivityCallback.Stub {
@@ -172,6 +175,7 @@
private final String mServicePackageName;
private final String mIntentAction;
private ServiceConnection mServiceConnection;
+ private UserManager mUserManager;
/**
* Creates a new instance of {@link SharedConnectivityManager}.
@@ -217,12 +221,14 @@
mContext = context;
mServicePackageName = servicePackageName;
mIntentAction = serviceIntentAction;
+ mUserManager = context.getSystemService(UserManager.class);
}
private void bind() {
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Log.i(TAG, "onServiceConnected");
mService = ISharedConnectivityService.Stub.asInterface(service);
synchronized (mProxyDataLock) {
if (!mCallbackProxyCache.isEmpty()) {
@@ -253,9 +259,45 @@
}
};
- mContext.bindService(
+ boolean result = mContext.bindService(
new Intent().setPackage(mServicePackageName).setAction(mIntentAction),
mServiceConnection, Context.BIND_AUTO_CREATE);
+ if (!result) {
+ if (DEBUG) Log.i(TAG, "bindService failed");
+ mServiceConnection = null;
+ if (mUserManager != null && !mUserManager.isUserUnlocked()) { // In direct boot mode
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+ } else {
+ synchronized (mProxyDataLock) {
+ if (!mCallbackProxyCache.isEmpty()) {
+ mCallbackProxyCache.keySet().forEach(
+ callback -> callback.onRegisterCallbackFailed(
+ new IllegalStateException(
+ "Failed to bind after user unlock")));
+ mCallbackProxyCache.clear();
+ }
+ }
+ }
+ }
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.unregisterReceiver(mBroadcastReceiver);
+ bind();
+ }
+ };
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public BroadcastReceiver getBroadcastReceiver() {
+ return mBroadcastReceiver;
}
private void registerCallbackInternal(SharedConnectivityClientCallback callback,
@@ -357,6 +399,13 @@
return false;
}
+ // Try to unregister the broadcast receiver to guard against memory leaks.
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {
+ // This is fine, it means the receiver was never registered or was already unregistered.
+ }
+
if (mService == null) {
boolean shouldUnbind;
synchronized (mProxyDataLock) {