summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/preloaded-classes-denylist1
-rw-r--r--core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java40
-rw-r--r--core/java/android/os/IUserManager.aidl1
-rw-r--r--core/java/android/os/UserManager.java24
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java15
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/locale_config.xml3
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java26
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java255
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-release.aarbin21364 -> 21513 bytes
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java55
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java3
-rw-r--r--media/Android.bp2
-rw-r--r--media/java/android/media/tv/AdResponse.java2
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/OWNERS2
-rw-r--r--packages/SystemUI/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/OWNERS20
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl24
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt178
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt259
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java27
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java14
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java89
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java77
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java11
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java4
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java24
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java30
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java3
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java18
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java6
-rw-r--r--services/core/java/com/android/server/wm/Transition.java37
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java29
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java199
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java53
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java4
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java47
64 files changed, 1825 insertions, 372 deletions
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index 502d8c6dadb1..02f2df6167a5 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -9,4 +9,3 @@ android.net.rtp.AudioGroup
android.net.rtp.AudioStream
android.net.rtp.RtpStream
java.util.concurrent.ThreadLocalRandom
-com.android.internal.jank.InteractionJankMonitor$InstanceHolder
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
index be573721936b..d6f191e31182 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -37,6 +37,7 @@ import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig;
import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest;
import android.os.PersistableBundle;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.vcn.util.PersistableBundleUtils;
@@ -58,6 +59,8 @@ import java.util.Set;
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
public final class IkeSessionParamsUtils {
+ private static final String TAG = IkeSessionParamsUtils.class.getSimpleName();
+
private static final String SERVER_HOST_NAME_KEY = "SERVER_HOST_NAME_KEY";
private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
private static final String LOCAL_ID_KEY = "LOCAL_ID_KEY";
@@ -72,6 +75,13 @@ public final class IkeSessionParamsUtils {
private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "NATT_KEEPALIVE_DELAY_SEC_KEY";
private static final String IKE_OPTIONS_KEY = "IKE_OPTIONS_KEY";
+ // TODO: b/243181760 Use the IKE API when they are exposed
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static final int IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION = 6;
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static final int IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES = 7;
+
private static final Set<Integer> IKE_OPTIONS = new ArraySet<>();
static {
@@ -80,6 +90,26 @@ public final class IkeSessionParamsUtils {
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_MOBIKE);
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500);
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT);
+ IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+ IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+ IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+ }
+
+ /**
+ * Check if an IKE option is supported in the IPsec module installed on the device
+ *
+ * <p>This method ensures caller to safely access options that are added between dessert
+ * releases.
+ */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static boolean isIkeOptionValid(int option) {
+ try {
+ new IkeSessionParams.Builder().addIkeOption(option);
+ return true;
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Option not supported; discarding: " + option);
+ return false;
+ }
}
/** Serializes an IkeSessionParams to a PersistableBundle. */
@@ -130,7 +160,7 @@ public final class IkeSessionParamsUtils {
// IKE_OPTION is defined in IKE module and added in the IkeSessionParams
final List<Integer> enabledIkeOptions = new ArrayList<>();
for (int option : IKE_OPTIONS) {
- if (params.hasIkeOption(option)) {
+ if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
enabledIkeOptions.add(option);
}
}
@@ -205,12 +235,16 @@ public final class IkeSessionParamsUtils {
// Clear IKE Options that are by default enabled
for (int option : IKE_OPTIONS) {
- builder.removeIkeOption(option);
+ if (isIkeOptionValid(option)) {
+ builder.removeIkeOption(option);
+ }
}
final int[] optionArray = in.getIntArray(IKE_OPTIONS_KEY);
for (int option : optionArray) {
- builder.addIkeOption(option);
+ if (isIkeOptionValid(option)) {
+ builder.addIkeOption(option);
+ }
}
return builder.build();
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 62ee408cffbf..fa6b1189f1da 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -76,6 +76,7 @@ interface IUserManager {
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
+ boolean isUserSwitcherEnabled(int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
int getUserSerialNumber(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ef04f641b0ee..ca0af2cf07af 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5246,23 +5246,13 @@ public class UserManager {
})
@UserHandleAware
public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) {
- if (!supportsMultipleUsers()) {
- return false;
- }
- if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId)) {
- return false;
- }
- // If Demo Mode is on, don't show user switcher
- if (isDeviceInDemoMode(mContext)) {
- return false;
- }
- // Check the Settings.Global.USER_SWITCHER_ENABLED that the user can toggle on/off.
- final boolean userSwitcherSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.USER_SWITCHER_ENABLED,
- Resources.getSystem().getBoolean(R.bool.config_showUserSwitcherByDefault) ? 1 : 0)
- != 0;
- if (!userSwitcherSettingOn) {
- return false;
+
+ try {
+ if (!mService.isUserSwitcherEnabled(mUserId)) {
+ return false;
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
// The feature is enabled. But is it worth showing?
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index d066945cb92d..72de78c148f8 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -292,10 +292,7 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
};
- private static class InstanceHolder {
- public static final InteractionJankMonitor INSTANCE =
- new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
- }
+ private static volatile InteractionJankMonitor sInstance;
private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
this::updateProperties;
@@ -387,7 +384,15 @@ public class InteractionJankMonitor {
* @return instance of InteractionJankMonitor
*/
public static InteractionJankMonitor getInstance() {
- return InstanceHolder.INSTANCE;
+ // Use DCL here since this method might be invoked very often.
+ if (sInstance == null) {
+ synchronized (InteractionJankMonitor.class) {
+ if (sInstance == null) {
+ sInstance = new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
+ }
+ }
+ }
+ return sInstance;
}
/**
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9890614c010c..b59cf7fd3bcb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5872,4 +5872,10 @@
<!-- The number of tasks to scan to get the visibility of Home -->
<integer name="config_maxScanTasksForHomeVisibility">10</integer>
+
+ <!-- Device state that corresponds to rear display mode, feature provided
+ through Jetpack WindowManager
+ TODO(b/236022708) Move rear display state to device state config file
+ -->
+ <integer name="config_deviceStateRearDisplay">-1</integer>
</resources>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index e9b42d392a2c..78ec14579bf1 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -23,7 +23,7 @@
<item>ak-GH</item> <!-- Akan (Ghana) -->
<item>am-ET</item> <!-- Amharic (Ethiopia) -->
<item>ar-AE</item> <!-- Arabic (United Arab Emirates) -->
- <item>ar-AE-u-nu-latn</item> <!-- Arabic (United Arab Emirates, Western Digits) -->
+ <item>ar-AE-u-nu-arab</item> <!-- Arabic (United Arab Emirates, Arabic Digits) -->
<item>ar-BH</item> <!-- Arabic (Bahrain) -->
<item>ar-BH-u-nu-latn</item> <!-- Arabic (Bahrain, Western Digits) -->
<item>ar-DJ</item> <!-- Arabic (Djibouti) -->
@@ -190,6 +190,7 @@
<item>en-MS</item> <!-- English (Montserrat) -->
<item>en-MT</item> <!-- English (Malta) -->
<item>en-MU</item> <!-- English (Mauritius) -->
+ <item>en-MV</item> <!-- English (Maldives) -->
<item>en-MW</item> <!-- English (Malawi) -->
<item>en-MY</item> <!-- English (Malaysia) -->
<item>en-NA</item> <!-- English (Namibia) -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0654fff661bd..d60fc209ebc1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4830,6 +4830,7 @@
<java-symbol type="drawable" name="ic_swap_horiz" />
<java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
<java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
+ <java-symbol type="integer" name="config_deviceStateRearDisplay"/>
<!-- For app language picker -->
<java-symbol type="string" name="system_locale_title" />
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index bdf703c9bd38..7e9c4189dabb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -20,11 +20,14 @@ import android.app.ActivityThread;
import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.area.WindowAreaComponentImpl;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.SplitController;
import androidx.window.extensions.layout.WindowLayoutComponent;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+
/**
* The reference implementation of {@link WindowExtensions} that implements the initial API version.
*/
@@ -33,10 +36,12 @@ public class WindowExtensionsImpl implements WindowExtensions {
private final Object mLock = new Object();
private volatile WindowLayoutComponent mWindowLayoutComponent;
private volatile SplitController mSplitController;
+ private volatile WindowAreaComponent mWindowAreaComponent;
+ // TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 1;
+ return 2;
}
/**
@@ -75,4 +80,23 @@ public class WindowExtensionsImpl implements WindowExtensions {
}
return mSplitController;
}
+
+ /**
+ * Returns a reference implementation of {@link WindowAreaComponent} if available,
+ * {@code null} otherwise. The implementation must match the API level reported in
+ * {@link WindowExtensions#getWindowAreaComponent()}.
+ * @return {@link WindowAreaComponent} OEM implementation.
+ */
+ public WindowAreaComponent getWindowAreaComponent() {
+ if (mWindowAreaComponent == null) {
+ synchronized (mLock) {
+ if (mWindowAreaComponent == null) {
+ Context context = ActivityThread.currentApplication();
+ mWindowAreaComponent =
+ new WindowAreaComponentImpl(context);
+ }
+ }
+ }
+ return mWindowAreaComponent;
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
new file mode 100644
index 000000000000..3adae7006369
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022 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.extensions.area;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Reference implementation of androidx.window.extensions.area OEM interface for use with
+ * WindowManager Jetpack.
+ *
+ * This component currently supports Rear Display mode with the ability to add and remove
+ * status listeners for this mode.
+ *
+ * The public methods in this class are thread-safe.
+ **/
+public class WindowAreaComponentImpl implements WindowAreaComponent,
+ DeviceStateManager.DeviceStateCallback {
+
+ private final Object mLock = new Object();
+
+ private final DeviceStateManager mDeviceStateManager;
+ private final Executor mExecutor;
+
+ @GuardedBy("mLock")
+ private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>();
+ private final int mRearDisplayState;
+ @WindowAreaSessionState
+ private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
+
+ @GuardedBy("mLock")
+ private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+ @GuardedBy("mLock")
+ private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE;
+ @GuardedBy("mLock")
+ private DeviceStateRequest mDeviceStateRequest;
+
+ public WindowAreaComponentImpl(@NonNull Context context) {
+ mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+ mExecutor = context.getMainExecutor();
+
+ // TODO(b/236022708) Move rear display state to device state config file
+ mRearDisplayState = context.getResources().getInteger(
+ R.integer.config_deviceStateRearDisplay);
+
+ mDeviceStateManager.registerCallback(mExecutor, this);
+ }
+
+ /**
+ * Adds a listener interested in receiving updates on the RearDisplayStatus
+ * of the device. Because this is being called from the OEM provided
+ * extensions, we will post the result of the listener on the executor
+ * provided by the developer at the initial call site.
+ *
+ * Depending on the initial state of the device, we will return either
+ * {@link WindowAreaComponent#STATUS_AVAILABLE} or
+ * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
+ * state respectively. When the rear display feature is triggered, we update the status to be
+ * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_
+ *
+ * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently
+ * enabled.
+ *
+ * @param consumer {@link Consumer} interested in receiving updates to the status of
+ * rear display mode.
+ */
+ public void addRearDisplayStatusListener(
+ @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
+ synchronized (mLock) {
+ mRearDisplayStatusListeners.add(consumer);
+
+ // If current device state is still invalid, we haven't gotten our initial value yet
+ if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+ return;
+ }
+ consumer.accept(getCurrentStatus());
+ }
+ }
+
+ /**
+ * Removes a listener no longer interested in receiving updates.
+ * @param consumer no longer interested in receiving updates to RearDisplayStatus
+ */
+ public void removeRearDisplayStatusListener(
+ @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
+ synchronized (mLock) {
+ mRearDisplayStatusListeners.remove(consumer);
+ }
+ }
+
+ /**
+ * Creates and starts a rear display session and provides updates to the
+ * callback provided. Because this is being called from the OEM provided
+ * extensions, we will post the result of the listener on the executor
+ * provided by the developer at the initial call site.
+ *
+ * When we enable rear display mode, we submit a request to {@link DeviceStateManager}
+ * to override the device state to the state that corresponds to RearDisplay
+ * mode. When the {@link DeviceStateRequest} is activated, we let the
+ * consumer know that the session is active by sending
+ * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}.
+ *
+ * @param activity to provide updates to the client on
+ * the status of the Session
+ * @param rearDisplaySessionCallback to provide updates to the client on
+ * the status of the Session
+ */
+ public void startRearDisplaySession(@NonNull Activity activity,
+ @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) {
+ synchronized (mLock) {
+ if (mDeviceStateRequest != null) {
+ // Rear display session is already active
+ throw new IllegalStateException(
+ "Unable to start new rear display session as one is already active");
+ }
+ mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
+ mDeviceStateManager.requestState(
+ mDeviceStateRequest,
+ mExecutor,
+ new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback)
+ );
+ }
+ }
+
+ /**
+ * Ends the current rear display session and provides updates to the
+ * callback provided. Because this is being called from the OEM provided
+ * extensions, we will post the result of the listener on the executor
+ * provided by the developer.
+ */
+ public void endRearDisplaySession() {
+ synchronized (mLock) {
+ if (mDeviceStateRequest != null || isRearDisplayActive()) {
+ mDeviceStateRequest = null;
+ mDeviceStateManager.cancelStateRequest();
+ } else {
+ throw new IllegalStateException(
+ "Unable to cancel a rear display session as there is no active session");
+ }
+ }
+ }
+
+ @Override
+ public void onBaseStateChanged(int state) {
+ synchronized (mLock) {
+ mCurrentDeviceBaseState = state;
+ if (state == mCurrentDeviceState) {
+ updateStatusConsumers(getCurrentStatus());
+ }
+ }
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ synchronized (mLock) {
+ mCurrentDeviceState = state;
+ updateStatusConsumers(getCurrentStatus());
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int getCurrentStatus() {
+ if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
+ || isRearDisplayActive()) {
+ return WindowAreaComponent.STATUS_UNAVAILABLE;
+ }
+ return WindowAreaComponent.STATUS_AVAILABLE;
+ }
+
+ /**
+ * Helper method to determine if a rear display session is currently active by checking
+ * if the current device configuration matches that of rear display. This would be true
+ * if there is a device override currently active (base state != current state) and the current
+ * state is that which corresponds to {@code mRearDisplayState}
+ * @return {@code true} if the device is in rear display mode and {@code false} if not
+ */
+ @GuardedBy("mLock")
+ private boolean isRearDisplayActive() {
+ return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState
+ == mRearDisplayState);
+ }
+
+ @GuardedBy("mLock")
+ private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) {
+ synchronized (mLock) {
+ for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) {
+ mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus);
+ }
+ }
+ }
+
+ /**
+ * Callback for the {@link DeviceStateRequest} to be notified of when the request has been
+ * activated or cancelled. This callback provides information to the client library
+ * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback}
+ */
+ private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback {
+
+ private final Consumer<Integer> mRearDisplaySessionCallback;
+
+ DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
+ mRearDisplaySessionCallback = callback;
+ }
+
+ @Override
+ public void onRequestActivated(@NonNull DeviceStateRequest request) {
+ synchronized (mLock) {
+ if (request.equals(mDeviceStateRequest)) {
+ mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE;
+ mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
+ updateStatusConsumers(getCurrentStatus());
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCanceled(DeviceStateRequest request) {
+ synchronized (mLock) {
+ if (request.equals(mDeviceStateRequest)) {
+ mDeviceStateRequest = null;
+ }
+ mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
+ mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
+ updateStatusConsumers(getCurrentStatus());
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 918e514f4c89..e9a1721fba2a 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 85c8ebf454c9..83ba909e712d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -80,7 +80,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
- private static final int FLING_ANIMATION_DURATION = 250;
+ private static final int FLING_RESIZE_DURATION = 250;
+ private static final int FLING_SWITCH_DURATION = 350;
+ private static final int FLING_ENTER_DURATION = 350;
+ private static final int FLING_EXIT_DURATION = 350;
private final int mDividerWindowWidth;
private final int mDividerInsets;
@@ -93,6 +96,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final Rect mBounds1 = new Rect();
// Bounds2 final position should be always at bottom or right
private final Rect mBounds2 = new Rect();
+ // The temp bounds outside of display bounds for side stage when split screen inactive to avoid
+ // flicker next time active split screen.
+ private final Rect mInvisibleBounds = new Rect();
private final Rect mWinBounds1 = new Rect();
private final Rect mWinBounds2 = new Rect();
private final SplitLayoutHandler mSplitLayoutHandler;
@@ -141,6 +147,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
resetDividerPosition();
mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+
+ mInvisibleBounds.set(mRootBounds);
+ mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
+ isLandscape() ? 0 : mRootBounds.bottom);
}
private int getDividerInsets(Resources resources, Display display) {
@@ -239,6 +249,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
rect.offset(-mRootBounds.left, -mRootBounds.top);
}
+ /** Gets bounds size equal to root bounds but outside of screen, used for position side stage
+ * when split inactive to avoid flicker when next time active. */
+ public void getInvisibleBounds(Rect rect) {
+ rect.set(mInvisibleBounds);
+ }
+
/** Returns leash of the current divider bar. */
@Nullable
public SurfaceControl getDividerLeash() {
@@ -284,6 +300,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
initDividerPosition(mTempRect);
+ mInvisibleBounds.set(mRootBounds);
+ mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
+ isLandscape() ? 0 : mRootBounds.bottom);
+
return true;
}
@@ -405,6 +425,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mFreezeDividerWindow = freezeDividerWindow;
}
+ /** Update current layout as divider put on start or end position. */
+ public void setDividerAtBorder(boolean start) {
+ final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
+ : mDividerSnapAlgorithm.getDismissEndTarget().position;
+ setDividePosition(pos, false /* applyLayoutChange */);
+ }
+
/**
* Updates bounds with the passing position. Usually used to update recording bounds while
* performing animation or dragging divider bar to resize the splits.
@@ -449,17 +476,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
switch (snapTarget.flag) {
case FLAG_DISMISS_START:
- flingDividePosition(currentPosition, snapTarget.position,
+ flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
case FLAG_DISMISS_END:
- flingDividePosition(currentPosition, snapTarget.position,
+ flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
default:
- flingDividePosition(currentPosition, snapTarget.position,
+ flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
break;
}
@@ -516,12 +543,20 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void flingDividerToDismiss(boolean toEnd, int reason) {
final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
: mDividerSnapAlgorithm.getDismissStartTarget().position;
- flingDividePosition(getDividePosition(), target,
+ flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
}
+ /** Fling divider from current position to center position. */
+ public void flingDividerToCenter() {
+ final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
+ flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION,
+ () -> setDividePosition(pos, true /* applyLayoutChange */));
+ }
+
@VisibleForTesting
- void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) {
+ void flingDividePosition(int from, int to, int duration,
+ @Nullable Runnable flingFinishedCallback) {
if (from == to) {
// No animation run, still callback to stop resizing.
mSplitLayoutHandler.onLayoutSizeChanged(this);
@@ -531,7 +566,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
ValueAnimator animator = ValueAnimator
.ofInt(from, to)
- .setDuration(FLING_ANIMATION_DURATION);
+ .setDuration(duration);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addUpdateListener(
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
@@ -588,7 +623,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
AnimatorSet set = new AnimatorSet();
set.playTogether(animator1, animator2, animator3);
- set.setDuration(FLING_ANIMATION_DURATION);
+ set.setDuration(FLING_SWITCH_DURATION);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7e83d2fa0a0b..21fc01e554c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -488,13 +489,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
- // If split still not active, apply windows bounds first to avoid surface reset to
- // wrong pos by SurfaceAnimator from wms.
- // TODO(b/223325631): check is it still necessary after improve enter transition done.
- if (!mMainStage.isActive()) {
- updateWindowBounds(mSplitLayout, wct);
- }
-
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
@@ -641,7 +635,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.startTask(sideTaskId, sideOptions);
}
// Using legacy transitions, so we can't use blast sync since it conflicts.
- mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
@@ -893,10 +887,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mShouldUpdateRecents = false;
mIsDividerRemoteAnimating = false;
+ mSplitLayout.getInvisibleBounds(mTempRect1);
if (childrenToTop == null) {
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
+ wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
// Expand to top side split as full screen for fading out decor animation and dismiss
@@ -907,27 +904,32 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
? mSideStage : mMainStage;
tempFullStage.resetBounds(wct);
wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token,
- mRootTaskInfo.configuration.smallestScreenWidthDp);
+ SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
dismissStage.dismiss(wct, false /* toTop */);
}
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
t.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null);
- t.setPosition(mMainStage.mRootLeash, 0, 0)
- .setPosition(mSideStage.mRootLeash, 0, 0);
t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer);
setDividerVisibility(false, t);
- // In this case, exit still under progress, fade out the split decor after first WCT
- // done and do remaining WCT after animation finished.
- if (childrenToTop != null) {
+ if (childrenToTop == null) {
+ t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
+ } else {
+ // In this case, exit still under progress, fade out the split decor after first WCT
+ // done and do remaining WCT after animation finished.
childrenToTop.fadeOutDecor(() -> {
WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
mIsExiting = false;
childrenToTop.dismiss(finishedWCT, true /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- mTaskOrganizer.applyTransaction(finishedWCT);
+ finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
+ finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
+ mSyncQueue.queue(finishedWCT);
+ mSyncQueue.runInSync(at -> {
+ at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
+ });
onTransitionAnimationComplete();
});
}
@@ -996,6 +998,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -1221,7 +1224,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- mTaskOrganizer.applyTransaction(wct);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
+ mSplitLayout.getInvisibleBounds(mTempRect1);
+ wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
+ });
}
private void onRootTaskVanished() {
@@ -1377,10 +1386,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// TODO (b/238697912) : Add the validation to prevent entering non-recovered status
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
- prepareEnterSplitScreen(wct);
+ mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ mMainStage.activate(wct, true /* includingTopTask */);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t ->
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+
+ mSplitLayout.flingDividerToCenter();
+ });
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
@@ -1822,6 +1838,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// properly for the animation itself.
mSplitLayout.release();
mSplitLayout.resetDividerPosition();
+ mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 95725bbfd855..695550dd8fa5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -159,7 +159,8 @@ public class SplitLayoutTests extends ShellTestCase {
}
private void waitDividerFlingFinished() {
- verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
+ verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
+ mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
}
diff --git a/media/Android.bp b/media/Android.bp
index d28a21cdf31f..ec243bf2370a 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -113,7 +113,7 @@ aidl_interface {
min_sdk_version: "29",
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
},
},
diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java
index 0c20954d1382..08c66aba07d5 100644
--- a/media/java/android/media/tv/AdResponse.java
+++ b/media/java/android/media/tv/AdResponse.java
@@ -25,7 +25,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * An advertisement request which can be sent to TV interactive App service to inform AD status.
+ * An advertisement response which can be sent to TV interactive App service to inform AD status.
*/
public final class AdResponse implements Parcelable {
/** @hide */
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 9f9288747185..a72f34cd03f3 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -217,7 +217,7 @@ public class ITvInteractiveAppSessionWrapper
case DO_DISPATCH_SURFACE_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
mSessionImpl.dispatchSurfaceChanged(
- (Integer) args.arg1, (Integer) args.arg2, (Integer) args.arg3);
+ (Integer) args.argi1, (Integer) args.argi2, (Integer) args.argi3);
args.recycle();
break;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
new file mode 100644
index 000000000000..d40f322042fe
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
@@ -0,0 +1,2 @@
+# Default reviewers for this and subdirectories.
+shaoweishen@google.com
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6edf13addbca..652e281e67e9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -394,6 +394,11 @@
android:label="@string/screenshot_scroll_label"
android:finishOnTaskLaunch="true" />
+ <service android:name=".screenshot.ScreenshotProxyService"
+ android:permission="com.android.systemui.permission.SELF"
+ android:exported="false" />
+
+
<service android:name=".screenrecord.RecordingService" />
<receiver android:name=".SysuiRestartReceiver"
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 61fac2906fb6..4a6164c1e15d 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -14,11 +14,10 @@ brzezinski@google.com
brycelee@google.com
ccassidy@google.com
chrisgollner@google.com
-cinek@google.com
-cwren@google.com
dupin@google.com
ethibodeau@google.com
evanlaird@google.com
+florenceyang@google.com
gwasserman@google.com
hwwang@google.com
hyunyoungs@google.com
@@ -27,36 +26,36 @@ jamesoleary@google.com
jbolinger@google.com
jdemeulenaere@google.com
jeffdq@google.com
+jernej@google.com
+jglazier@google.com
jjaggi@google.com
jonmiranda@google.com
joshtrask@google.com
juliacr@google.com
juliatuttle@google.com
justinkoh@google.com
-kchyn@google.com
kozynski@google.com
kprevas@google.com
lynhan@google.com
madym@google.com
mankoff@google.com
-mett@google.com
mkephart@google.com
mpietal@google.com
mrcasey@google.com
mrenouf@google.com
-nesciosquid@google.com
nickchameyev@google.com
nicomazz@google.com
ogunwale@google.com
peanutbutter@google.com
+peskal@google.com
pinyaoting@google.com
pixel@google.com
+pomini@google.com
rahulbanerjee@google.com
roosa@google.com
santie@google.com
shanh@google.com
snoeberger@google.com
-sreyasr@google.com
steell@google.com
sfufa@google.com
stwu@google.com
@@ -70,15 +69,10 @@ vadimt@google.com
victortulias@google.com
winsonc@google.com
wleshner@google.com
-yurilin@google.com
xuqiu@google.com
+yuandizhou@google.com
+yurilin@google.com
zakcohen@google.com
-jernej@google.com
-jglazier@google.com
-peskal@google.com
-
-#Android Auto
-hseog@google.com
#Android TV
rgl@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 49e378e4a76f..d96476fbf087 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -99,12 +99,11 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
+ mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
if (!mIsAccessibilityEnabled) {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
- mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
} else {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
- mOnFirstBucketFailedColor = mHelpColor;
}
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
mCheckmarkDrawable.mutate();
@@ -167,7 +166,8 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
}
private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
- if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
+ if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps
+ && mShowingHelp == showingHelp) {
return;
}
@@ -197,6 +197,7 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
}
}
+ mShowingHelp = showingHelp;
mRemainingSteps = remainingSteps;
mTotalSteps = totalSteps;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f32ea353bf50..2dadf573fd12 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -18,6 +18,7 @@ package com.android.systemui.dagger;
import android.app.INotificationManager;
import android.content.Context;
+import android.service.dreams.IDreamManager;
import androidx.annotation.Nullable;
@@ -213,6 +214,7 @@ public abstract class SystemUIModule {
ShadeController shadeController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
+ IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
ZenModeController zenModeController,
@@ -230,6 +232,7 @@ public abstract class SystemUIModule {
shadeController,
statusBarService,
notificationManager,
+ dreamManager,
visibilityProvider,
interruptionStateProvider,
zenModeController,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 4b409f5255fb..1f356cb3d18c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -237,6 +237,7 @@ public class Flags {
// 1300 - screenshots
public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
+ public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
// 1400 - columbus, b/242800729
public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 3eb3c80f4081..6dfbd426ef30 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -323,6 +323,8 @@ public class KeyguardService extends Service {
if (sEnableRemoteKeyguardOccludeAnimation) {
Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
// Register for occluding
+ final RemoteTransition occludeTransition = new RemoteTransition(
+ mOccludeAnimation, getIApplicationThread());
TransitionFilter f = new TransitionFilter();
f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
f.mRequirements = new TransitionFilter.Requirement[]{
@@ -337,10 +339,11 @@ public class KeyguardService extends Service {
f.mRequirements[1].mMustBeIndependent = false;
f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD;
f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
- mShellTransitions.registerRemote(f,
- new RemoteTransition(mOccludeAnimation, getIApplicationThread()));
+ mShellTransitions.registerRemote(f, occludeTransition);
// Now register for un-occlude.
+ final RemoteTransition unoccludeTransition = new RemoteTransition(
+ mUnoccludeAnimation, getIApplicationThread());
f = new TransitionFilter();
f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
f.mRequirements = new TransitionFilter.Requirement[]{
@@ -358,8 +361,23 @@ public class KeyguardService extends Service {
f.mRequirements[0].mMustBeIndependent = false;
f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- mShellTransitions.registerRemote(f,
- new RemoteTransition(mUnoccludeAnimation, getIApplicationThread()));
+ mShellTransitions.registerRemote(f, unoccludeTransition);
+
+ // Register for specific transition type.
+ // Above filter cannot fulfill all conditions.
+ // E.g. close top activity while screen off but next activity is occluded, this should
+ // an occluded transition, but since the activity is invisible, the condition would
+ // match unoccluded transition.
+ // But on the contrary, if we add above condition in occluded transition, then when user
+ // trying to dismiss occluded activity when unlock keyguard, the condition would match
+ // occluded transition.
+ f = new TransitionFilter();
+ f.mTypeSet = new int[]{TRANSIT_KEYGUARD_OCCLUDE};
+ mShellTransitions.registerRemote(f, occludeTransition);
+
+ f = new TransitionFilter();
+ f.mTypeSet = new int[]{TRANSIT_KEYGUARD_UNOCCLUDE};
+ mShellTransitions.registerRemote(f, unoccludeTransition);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b4f40e2fa670..4a7346ee2901 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2377,10 +2377,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
private void handleHide() {
Trace.beginSection("KeyguardViewMediator#handleHide");
- // It's possible that the device was unlocked in a dream state. It's time to wake up.
- if (mAodShowing || mDreamOverlayShowing) {
- PowerManager pm = mContext.getSystemService(PowerManager.class);
- pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ // It's possible that the device was unlocked (via BOUNCER) while dozing. It's time to
+ // wake up.
+ if (mAodShowing) {
+ mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
"com.android.systemui:BOUNCER_DOZING");
}
@@ -2409,6 +2409,13 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
null /* nonApps */, null /* finishedCallback */);
});
}
+
+ // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while
+ // dreaming. It's time to wake up.
+ if (mDreamOverlayShowing) {
+ mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ "com.android.systemui:UNLOCK_DREAMING");
+ }
}
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 88a1b17e37fe..7b497ade683c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media
import android.app.Notification
+import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
@@ -27,6 +28,7 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
@@ -57,8 +59,8 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
@@ -633,9 +635,14 @@ class MediaDataManager(
}
val mediaController = mediaControllerFactory.create(token)
val metadata = mediaController.metadata
+ val notif: Notification = sbn.notification
+
+ val appInfo = notif.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ ) ?: getAppInfoFromPackage(sbn.packageName)
// Album art
- val notif: Notification = sbn.notification
var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
if (artworkBitmap == null) {
artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
@@ -650,8 +657,7 @@ class MediaDataManager(
}
// App name
- val builder = Notification.Builder.recoverBuilder(context, notif)
- val app = builder.loadHeaderAppName()
+ val appName = getAppName(sbn, appInfo)
// App Icon
val smallIcon = sbn.notification.smallIcon
@@ -712,12 +718,7 @@ class MediaDataManager(
val currentEntry = mediaEntries.get(key)
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
- val appUid = try {
- context.packageManager.getApplicationInfo(sbn.packageName, 0)?.uid!!
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "Could not get app UID for ${sbn.packageName}", e)
- Process.INVALID_UID
- }
+ val appUid = appInfo?.uid ?: Process.INVALID_UID
if (logEvent) {
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
@@ -730,7 +731,7 @@ class MediaDataManager(
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
val active = mediaEntries[key]?.active ?: true
- onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, app,
+ onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, appName,
smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
semanticActions, sbn.packageName, token, notif.contentIntent, device,
active, resumeAction = resumeAction, playbackLocation = playbackLocation,
@@ -740,6 +741,28 @@ class MediaDataManager(
}
}
+ private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
+ try {
+ return context.packageManager.getApplicationInfo(packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app info for $packageName", e)
+ }
+ return null
+ }
+
+ private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
+ val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
+ if (name != null) {
+ return name
+ }
+
+ return if (appInfo != null) {
+ context.packageManager.getApplicationLabel(appInfo).toString()
+ } else {
+ sbn.packageName
+ }
+ }
+
/**
* Generate action buttons based on notification actions
*/
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
new file mode 100644
index 000000000000..f7c4dadc6605
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2009, 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.screenshot;
+
+/** Interface implemented by ScreenshotProxyService */
+interface IScreenshotProxy {
+
+ /** Is the notification shade currently exanded? */
+ boolean isNotificationShadeExpanded();
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
index 39f35a59ff42..77797601ca5a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
@@ -22,5 +22,5 @@ interface ImageCapture {
fun captureDisplay(displayId: Int, crop: Rect? = null): Bitmap?
- fun captureTask(taskId: Int): Bitmap?
+ suspend fun captureTask(taskId: Int): Bitmap?
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
index 258c4360922d..246265b2c202 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
@@ -27,13 +27,19 @@ import android.view.SurfaceControl
import android.view.SurfaceControl.DisplayCaptureArgs
import android.view.SurfaceControl.ScreenshotHardwareBuffer
import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
private const val TAG = "ImageCaptureImpl"
+@SysUISingleton
open class ImageCaptureImpl @Inject constructor(
private val displayManager: DisplayManager,
- private val atmService: IActivityTaskManager
+ private val atmService: IActivityTaskManager,
+ @Background private val bgContext: CoroutineDispatcher
) : ImageCapture {
override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
@@ -46,8 +52,8 @@ open class ImageCaptureImpl @Inject constructor(
return buffer?.asBitmap()
}
- override fun captureTask(taskId: Int): Bitmap? {
- val snapshot = atmService.takeTaskSnapshot(taskId)
+ override suspend fun captureTask(taskId: Int): Bitmap? {
+ val snapshot = withContext(bgContext) { atmService.takeTaskSnapshot(taskId) } ?: return null
return Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
}
@@ -67,12 +73,17 @@ open class ImageCaptureImpl @Inject constructor(
}
@VisibleForTesting
- open fun captureDisplay(displayToken: IBinder, width: Int, height: Int, crop: Rect): ScreenshotHardwareBuffer? {
- val captureArgs = DisplayCaptureArgs.Builder(displayToken)
- .setSize(width, height)
- .setSourceCrop(crop)
- .build()
+ open fun captureDisplay(
+ displayToken: IBinder,
+ width: Int,
+ height: Int,
+ crop: Rect
+ ): ScreenshotHardwareBuffer? {
+ val captureArgs =
+ DisplayCaptureArgs.Builder(displayToken)
+ .setSize(width, height)
+ .setSourceCrop(crop)
+ .build()
return SurfaceControl.captureDisplay(captureArgs)
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 4397d3d85d62..a918e5d9e106 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -16,51 +16,84 @@
package com.android.systemui.screenshot
-import android.net.Uri
-import android.util.Log
-import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.graphics.Insets
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* Processes a screenshot request sent from {@link ScreenshotHelper}.
*/
@SysUISingleton
-internal class RequestProcessor @Inject constructor(
- private val controller: ScreenshotController,
+class RequestProcessor @Inject constructor(
+ private val capture: ImageCapture,
+ private val policy: ScreenshotPolicy,
+ private val flags: FeatureFlags,
+ /** For the Java Async version, to invoke the callback. */
+ @Application private val mainScope: CoroutineScope
) {
- fun processRequest(
- request: ScreenshotRequest,
- onSavedListener: Consumer<Uri>,
- callback: RequestCallback
- ) {
+ /**
+ * Inspects the incoming request, returning a potentially modified request depending on policy.
+ *
+ * @param request the request to process
+ */
+ suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
+ var result = request
- if (request.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
- val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle)
+ // Apply work profile screenshots policy:
+ //
+ // If the focused app belongs to a work profile, transforms a full screen
+ // (or partial) screenshot request to a task snapshot (provided image) screenshot.
- controller.handleImageAsScreenshot(
- image, request.boundsInScreen, request.insets,
- request.taskId, request.userId, request.topComponent, onSavedListener, callback
- )
- return
- }
+ // Whenever displayContentInfo is fetched, the topComponent is also populated
+ // regardless of the managed profile status.
+
+ if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
+ flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ ) {
+
+ val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
+
+ result = if (policy.isManagedProfile(info.userId)) {
+ val image = capture.captureTask(info.taskId)
+ ?: error("Task snapshot returned a null Bitmap!")
- when (request.type) {
- TAKE_SCREENSHOT_FULLSCREEN ->
- controller.takeScreenshotFullscreen(null, onSavedListener, callback)
- TAKE_SCREENSHOT_SELECTED_REGION ->
- controller.takeScreenshotPartial(null, onSavedListener, callback)
- else -> Log.w(TAG, "Invalid screenshot option: ${request.type}")
+ // Provide the task snapshot as the screenshot
+ ScreenshotRequest(
+ TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
+ HardwareBitmapBundler.hardwareBitmapToBundle(image),
+ info.bounds, Insets.NONE, info.taskId, info.userId, info.component
+ )
+ } else {
+ // Create a new request of the same type which includes the top component
+ ScreenshotRequest(request.source, request.type, info.component)
+ }
}
+
+ return result
}
- companion object {
- const val TAG: String = "RequestProcessor"
+ /**
+ * Note: This is for compatibility with existing Java. Prefer the suspending function when
+ * calling from a Coroutine context.
+ *
+ * @param request the request to process
+ * @param callback the callback to provide the processed request, invoked from the main thread
+ */
+ fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
+ mainScope.launch {
+ val result = process(request)
+ callback.accept(result)
+ }
}
}
+
+private const val TAG = "RequestProcessor"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
new file mode 100644
index 000000000000..3580010cc1e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.screenshot
+
+import android.annotation.UserIdInt
+import android.content.ComponentName
+import android.graphics.Rect
+import android.view.Display
+
+/**
+ * Provides policy decision-making information to screenshot request handling.
+ */
+interface ScreenshotPolicy {
+
+ /** @return true if the user is a managed profile (a.k.a. work profile) */
+ suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean
+
+ /**
+ * Requests information about the owner of display content which occupies a majority of the
+ * screenshot and/or has most recently been interacted with at the time the screenshot was
+ * requested.
+ *
+ * @param displayId the id of the display to inspect
+ * @return content info for the primary content on the display
+ */
+ suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo
+
+ data class DisplayContentInfo(
+ val component: ComponentName,
+ val bounds: Rect,
+ @UserIdInt val userId: Int,
+ val taskId: Int,
+ )
+
+ fun getDefaultDisplayId(): Int = Display.DEFAULT_DISPLAY
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
new file mode 100644
index 000000000000..ba809f676f1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 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.screenshot
+
+import android.annotation.UserIdInt
+import android.app.ActivityTaskManager
+import android.app.ActivityTaskManager.RootTaskInfo
+import android.app.IActivityTaskManager
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.activityTypeToString
+import android.app.WindowConfiguration.windowingModeToString
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Process
+import android.os.RemoteException
+import android.os.UserManager
+import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.SystemUIService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+import java.util.Arrays
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+internal class ScreenshotPolicyImpl @Inject constructor(
+ context: Context,
+ private val userMgr: UserManager,
+ private val atmService: IActivityTaskManager,
+ @Background val bgDispatcher: CoroutineDispatcher,
+) : ScreenshotPolicy {
+
+ private val systemUiContent =
+ DisplayContentInfo(
+ ComponentName(context, SystemUIService::class.java),
+ Rect(),
+ ActivityTaskManager.INVALID_TASK_ID,
+ Process.myUserHandle().identifier,
+ )
+
+ private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+ ServiceConnector.Impl(
+ context,
+ Intent(context, ScreenshotProxyService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ context.userId,
+ IScreenshotProxy.Stub::asInterface
+ )
+
+ override fun getDefaultDisplayId(): Int {
+ return DEFAULT_DISPLAY
+ }
+
+ override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
+ return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+ }
+
+ private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
+ return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
+ info.isVisible &&
+ info.isRunning &&
+ info.numActivities > 0 &&
+ info.topActivity != null &&
+ info.childTaskIds.isNotEmpty()
+ }
+
+ /**
+ * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a
+ * display. If no task is visible or the top task is covered by a system window, the info
+ * reported will reference a SystemUI component instead.
+ */
+ override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
+ // Determine if the notification shade is expanded. If so, task windows are not
+ // visible behind it, so the screenshot should instead be associated with SystemUI.
+ if (isNotificationShadeExpanded()) {
+ return systemUiContent
+ }
+
+ val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)
+ if (DEBUG) {
+ debugLogRootTaskInfos(taskInfoList)
+ }
+
+ // If no visible task is located, then report SystemUI as the foreground content
+ val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
+
+ val topActivity: ComponentName = target.topActivity ?: error("should not be null")
+ val topChildTask = target.childTaskIds.size - 1
+ val childTaskId = target.childTaskIds[topChildTask]
+ val childTaskUserId = target.childTaskUserIds[topChildTask]
+ val childTaskBounds = target.childTaskBounds[topChildTask]
+
+ return DisplayContentInfo(topActivity, childTaskBounds, childTaskId, childTaskUserId)
+ }
+
+ private fun debugLogRootTaskInfos(taskInfoList: List<RootTaskInfo>) {
+ for (info in taskInfoList) {
+ Log.d(
+ TAG,
+ "[root task info] " +
+ "taskId=${info.taskId} " +
+ "parentTaskId=${info.parentTaskId} " +
+ "position=${info.position} " +
+ "positionInParent=${info.positionInParent} " +
+ "isVisible=${info.isVisible()} " +
+ "visible=${info.visible} " +
+ "isFocused=${info.isFocused} " +
+ "isSleeping=${info.isSleeping} " +
+ "isRunning=${info.isRunning} " +
+ "windowMode=${windowingModeToString(info.windowingMode)} " +
+ "activityType=${activityTypeToString(info.activityType)} " +
+ "topActivity=${info.topActivity} " +
+ "topActivityInfo=${info.topActivityInfo} " +
+ "numActivities=${info.numActivities} " +
+ "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
+ "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
+ "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
+ "childTaskNames=${Arrays.toString(info.childTaskNames)}"
+ )
+
+ for (j in 0 until info.childTaskIds.size) {
+ Log.d(TAG, " *** [$j] ******")
+ Log.d(TAG, " *** childTaskIds[$j]: ${info.childTaskIds[j]}")
+ Log.d(TAG, " *** childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
+ Log.d(TAG, " *** childTaskBounds[$j]: ${info.childTaskBounds[j]}")
+ Log.d(TAG, " *** childTaskNames[$j]: ${info.childTaskNames[j]}")
+ }
+ }
+ }
+
+ private suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
+ withContext(bgDispatcher) {
+ try {
+ atmService.getAllRootTaskInfosOnDisplay(displayId)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "getAllRootTaskInfosOnDisplay", e)
+ listOf()
+ }
+ }
+
+ private suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
+ proxyConnector
+ .postForResult { it.isNotificationShadeExpanded }
+ .whenComplete { expanded, error ->
+ if (error != null) {
+ Log.e(TAG, "isNotificationShadeExpanded", error)
+ }
+ k.resume(expanded ?: false)
+ }
+ }
+
+ companion object {
+ const val TAG: String = "ScreenshotPolicyImpl"
+ const val DEBUG: Boolean = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
new file mode 100644
index 000000000000..9654e03e506e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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.screenshot
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import android.util.Log
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
+import javax.inject.Inject
+
+/**
+ * Provides state from the main SystemUI process on behalf of the Screenshot process.
+ */
+internal class ScreenshotProxyService @Inject constructor(
+ private val mExpansionMgr: PanelExpansionStateManager
+) : Service() {
+
+ private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
+ /**
+ * @return true when the notification shade is partially or fully expanded.
+ */
+ override fun isNotificationShadeExpanded(): Boolean {
+ val expanded = !mExpansionMgr.isClosed()
+ Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
+ return expanded
+ }
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ Log.d(TAG, "onBind: $intent")
+ return mBinder
+ }
+
+ companion object {
+ const val TAG = "ScreenshotProxyService"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 7bf3217e5f15..a8993bc274e4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -22,6 +22,7 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR;
+import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
@@ -97,7 +98,7 @@ public class TakeScreenshotService extends Service {
};
/** Informs about coarse grained state of the Controller. */
- interface RequestCallback {
+ public interface RequestCallback {
/** Respond to the current request indicating the screenshot request failed. */
void reportError();
@@ -124,6 +125,7 @@ public class TakeScreenshotService extends Service {
mBgExecutor = bgExecutor;
mFeatureFlags = featureFlags;
mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart);
+ mFeatureFlags.addListener(SCREENSHOT_WORK_PROFILE_POLICY, FlagEvent::requestNoRestart);
mProcessor = processor;
}
@@ -229,49 +231,57 @@ public class TakeScreenshotService extends Service {
if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
Log.d(TAG, "handleMessage: Using request processor");
- mProcessor.processRequest(screenshotRequest, uriConsumer, requestCallback);
+ mProcessor.processAsync(screenshotRequest,
+ (request) -> dispatchToController(request, uriConsumer, requestCallback));
return true;
}
- switch (screenshotRequest.getType()) {
+ dispatchToController(screenshotRequest, uriConsumer, requestCallback);
+ return true;
+ }
+
+ private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
+ Consumer<Uri> uriConsumer, RequestCallback callback) {
+
+ ComponentName topComponent = request.getTopComponent();
+
+ switch (request.getType()) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
}
- mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback);
+ mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback);
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
}
- mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback);
+ mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, callback);
break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
}
Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
- screenshotRequest.getBitmapBundle());
- Rect screenBounds = screenshotRequest.getBoundsInScreen();
- Insets insets = screenshotRequest.getInsets();
- int taskId = screenshotRequest.getTaskId();
- int userId = screenshotRequest.getUserId();
+ request.getBitmapBundle());
+ Rect screenBounds = request.getBoundsInScreen();
+ Insets insets = request.getInsets();
+ int taskId = request.getTaskId();
+ int userId = request.getUserId();
if (screenshot == null) {
Log.e(TAG, "Got null bitmap from screenshot message");
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
- requestCallback.reportError();
+ callback.reportError();
} else {
mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
- taskId, userId, topComponent, uriConsumer, requestCallback);
+ taskId, userId, topComponent, uriConsumer, callback);
}
break;
default:
- Log.w(TAG, "Invalid screenshot option: " + msg.what);
- return false;
+ Log.w(TAG, "Invalid screenshot option: " + request.getType());
}
- return true;
}
private static void sendComplete(Messenger target) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 3e44258744f2..fdb01000b837 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -20,6 +20,9 @@ import android.app.Service;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.ScreenshotPolicy;
+import com.android.systemui.screenshot.ScreenshotPolicyImpl;
+import com.android.systemui.screenshot.ScreenshotProxyService;
import com.android.systemui.screenshot.TakeScreenshotService;
import dagger.Binds;
@@ -33,12 +36,20 @@ import dagger.multibindings.IntoMap;
@Module
public abstract class ScreenshotModule {
- /** */
@Binds
@IntoMap
@ClassKey(TakeScreenshotService.class)
- public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+ abstract Service bindTakeScreenshotService(TakeScreenshotService service);
@Binds
- public abstract ImageCapture bindImageCapture(ImageCaptureImpl capture);
+ @IntoMap
+ @ClassKey(ScreenshotProxyService.class)
+ abstract Service bindScreenshotProxyService(ScreenshotProxyService service);
+
+ @Binds
+ abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl);
+
+ @Binds
+ abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 65ba5adddd60..e754d5db4186 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -512,7 +512,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
} else {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
- mUpdateMonitor.awakenFromDream();
+ // Don't call awaken from Dream here. In order to avoid flickering, wait until
+ // later to awaken.
}
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
mKeyguardViewMediator.onWakeAndUnlocking();
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4c762702892a..aeab2dff9421 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -38,6 +38,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.ZenModeConfig;
import android.util.Log;
@@ -101,6 +102,7 @@ public class BubblesManager implements Dumpable {
private final ShadeController mShadeController;
private final IStatusBarService mBarService;
private final INotificationManager mNotificationManager;
+ private final IDreamManager mDreamManager;
private final NotificationVisibilityProvider mVisibilityProvider;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final NotificationLockscreenUserManager mNotifUserManager;
@@ -126,6 +128,7 @@ public class BubblesManager implements Dumpable {
ShadeController shadeController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
+ IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
ZenModeController zenModeController,
@@ -144,6 +147,7 @@ public class BubblesManager implements Dumpable {
shadeController,
statusBarService,
notificationManager,
+ dreamManager,
visibilityProvider,
interruptionStateProvider,
zenModeController,
@@ -167,6 +171,7 @@ public class BubblesManager implements Dumpable {
ShadeController shadeController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
+ IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
ZenModeController zenModeController,
@@ -182,6 +187,7 @@ public class BubblesManager implements Dumpable {
mNotificationShadeWindowController = notificationShadeWindowController;
mShadeController = shadeController;
mNotificationManager = notificationManager;
+ mDreamManager = dreamManager;
mVisibilityProvider = visibilityProvider;
mNotificationInterruptStateProvider = interruptionStateProvider;
mNotifUserManager = notifUserManager;
@@ -203,7 +209,7 @@ public class BubblesManager implements Dumpable {
@Override
public void onKeyguardShowingChanged() {
boolean isUnlockedShade = !keyguardStateController.isShowing()
- && !keyguardStateController.isOccluded();
+ && !isDreamingOrInPreview();
bubbles.onStatusBarStateChanged(isUnlockedShade);
}
});
@@ -397,6 +403,15 @@ public class BubblesManager implements Dumpable {
mBubbles.setSysuiProxy(mSysuiProxy);
}
+ private boolean isDreamingOrInPreview() {
+ try {
+ return mDreamManager.isDreamingOrInPreview();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query dream manager.", e);
+ return false;
+ }
+ }
+
private void setupNotifPipeline() {
mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 1cce7cfb5b8a..d1ed8e983cdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -50,11 +50,10 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
@@ -287,6 +286,30 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
+ fun testOnNotificationAdded_hasSubstituteName_isUsed() {
+ val subName = "Substitute Name"
+ val notif = SbnBuilder().run {
+ modifyNotification(context).also {
+ it.extras = Bundle().apply {
+ putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName)
+ }
+ it.setStyle(MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ })
+ }
+ build()
+ }
+
+ mediaDataManager.onNotificationAdded(KEY, notif)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0), eq(false))
+
+ assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName)
+ }
+
+ @Test
fun testLoadMediaDataInBg_invalidTokenNoCrash() {
val bundle = Bundle()
// wrong data type
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt
new file mode 100644
index 000000000000..447e28cd9527
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+
+internal class FakeImageCapture : ImageCapture {
+
+ var requestedDisplayId: Int? = null
+ var requestedDisplayCrop: Rect? = null
+ var requestedTaskId: Int? = null
+
+ var image: Bitmap? = null
+
+ override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
+ requestedDisplayId = displayId
+ requestedDisplayCrop = crop
+ return image
+ }
+
+ override suspend fun captureTask(taskId: Int): Bitmap? {
+ requestedTaskId = taskId
+ return image
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
new file mode 100644
index 000000000000..28d53c72640f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.screenshot
+
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+
+internal class FakeScreenshotPolicy : ScreenshotPolicy {
+
+ private val userTypes = mutableMapOf<Int, Boolean>()
+ private val contentInfo = mutableMapOf<Int, DisplayContentInfo?>()
+
+ fun setManagedProfile(userId: Int, managedUser: Boolean) {
+ userTypes[userId] = managedUser
+ }
+ override suspend fun isManagedProfile(userId: Int): Boolean {
+ return userTypes[userId] ?: error("No managedProfile value set for userId $userId")
+ }
+
+ fun setDisplayContentInfo(userId: Int, contentInfo: DisplayContentInfo) {
+ this.contentInfo[userId] = contentInfo
+ }
+
+ override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
+ return contentInfo[displayId] ?: error("No DisplayContentInfo set for displayId $displayId")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
index ce3f20d4d39d..00f38081c5c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
@@ -27,6 +27,8 @@ import android.view.SurfaceControl.ScreenshotHardwareBuffer
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +39,10 @@ import org.junit.runner.RunWith
class ImageCaptureImplTest : SysuiTestCase() {
private val displayManager = mock<DisplayManager>()
private val atmService = mock<IActivityTaskManager>()
- private val capture = TestableImageCaptureImpl(displayManager, atmService)
+ private val capture = TestableImageCaptureImpl(
+ displayManager,
+ atmService,
+ Dispatchers.Unconfined)
@Test
fun captureDisplayWithCrop() {
@@ -59,9 +64,10 @@ class ImageCaptureImplTest : SysuiTestCase() {
class TestableImageCaptureImpl(
displayManager: DisplayManager,
- atmService: IActivityTaskManager
+ atmService: IActivityTaskManager,
+ bgDispatcher: CoroutineDispatcher
) :
- ImageCaptureImpl(displayManager, atmService) {
+ ImageCaptureImpl(displayManager, atmService, bgDispatcher) {
var token: IBinder? = null
var width: Int? = null
@@ -81,4 +87,4 @@ class ImageCaptureImplTest : SysuiTestCase() {
return ScreenshotHardwareBuffer(null, null, false, false)
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 024d3bd8eb0e..48fbd354b98d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -22,86 +22,253 @@ import android.graphics.ColorSpace
import android.graphics.Insets
import android.graphics.Rect
import android.hardware.HardwareBuffer
-import android.net.Uri
+import android.os.Bundle
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-
+import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
+import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
-import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import com.google.common.truth.Truth.assertThat
-import java.util.function.Consumer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
import org.junit.Test
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.isNull
+
+private const val USER_ID = 1
+private const val TASK_ID = 1
class RequestProcessorTest {
- private val controller = mock<ScreenshotController>()
- private val bitmapCaptor = argumentCaptor<Bitmap>()
+ private val imageCapture = FakeImageCapture()
+ private val component = ComponentName("android.test", "android.test.Component")
+ private val bounds = Rect(25, 25, 75, 75)
+
+ private val scope = CoroutineScope(Dispatchers.Unconfined)
+ private val dispatcher = Dispatchers.Unconfined
+ private val policy = FakeScreenshotPolicy()
+ private val flags = FakeFeatureFlags()
+
+ /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
+ @Test
+ fun testProcessAsync() {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ var result: ScreenshotRequest? = null
+ var callbackCount = 0
+ val callback: (ScreenshotRequest) -> Unit = { processedRequest: ScreenshotRequest ->
+ result = processedRequest
+ callbackCount++
+ }
+
+ // runs synchronously, using Unconfined Dispatcher
+ processor.processAsync(request, callback)
+
+ // Callback invoked once returning the same request (no changes)
+ assertThat(callbackCount).isEqualTo(1)
+ assertThat(result).isEqualTo(request)
+ }
+
+ @Test
+ fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ val processedRequest = processor.process(request)
+
+ // No changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
@Test
- fun testFullScreenshot() {
+ fun testFullScreenshot() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ // Indicate that the primary content belongs to a normal user
+ policy.setManagedProfile(USER_ID, false)
+ policy.setDisplayContentInfo(
+ policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
- val onSavedListener = mock<Consumer<Uri>>()
- val callback = mock<RequestCallback>()
- val processor = RequestProcessor(controller)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
- processor.processRequest(request, onSavedListener, callback)
+ val processedRequest = processor.process(request)
- verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
- eq(onSavedListener), eq(callback))
+ // Request has topComponent added, but otherwise unchanged.
+ assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
}
@Test
- fun testSelectedRegionScreenshot() {
+ fun testFullScreenshot_managedProfile() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ // Provide a fake task bitmap when asked
+ val bitmap = makeHardwareBitmap(100, 100)
+ imageCapture.image = bitmap
+
+ // Indicate that the primary content belongs to a manged profile
+ policy.setManagedProfile(USER_ID, true)
+ policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ val processedRequest = processor.process(request)
+
+ // Expect a task snapshot is taken, overriding the full screen mode
+ assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
+ assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+ assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
+ assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
+ assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
+ assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
+ assertThat(processedRequest.userId).isEqualTo(USER_ID)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
+ }
+
+ @Test
+ fun testSelectedRegionScreenshot_workProfilePolicyDisabled() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ val processedRequest = processor.process(request)
+
+ // No changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
+
+ @Test
+ fun testSelectedRegionScreenshot() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
- val onSavedListener = mock<Consumer<Uri>>()
- val callback = mock<RequestCallback>()
- val processor = RequestProcessor(controller)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
- processor.processRequest(request, onSavedListener, callback)
+ policy.setManagedProfile(USER_ID, false)
+ policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
- verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
- eq(onSavedListener), eq(callback))
+ val processedRequest = processor.process(request)
+
+ // Request has topComponent added, but otherwise unchanged.
+ assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
+ }
+
+ @Test
+ fun testSelectedRegionScreenshot_managedProfile() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ // Provide a fake task bitmap when asked
+ val bitmap = makeHardwareBitmap(100, 100)
+ imageCapture.image = bitmap
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ // Indicate that the primary content belongs to a manged profile
+ policy.setManagedProfile(USER_ID, true)
+ policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+ val processedRequest = processor.process(request)
+
+ // Expect a task snapshot is taken, overriding the selected region mode
+ assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
+ assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+ assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
+ assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
+ assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
+ assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
+ assertThat(processedRequest.userId).isEqualTo(USER_ID)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
}
@Test
- fun testProvidedImageScreenshot() {
- val taskId = 1111
- val userId = 2222
+ fun testProvidedImageScreenshot_workProfilePolicyDisabled() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
val bounds = Rect(50, 50, 150, 150)
- val topComponent = ComponentName("test", "test")
- val processor = RequestProcessor(controller)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
- val buffer = HardwareBuffer.create(100, 100, HardwareBuffer.RGBA_8888, 1,
- HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
- val bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
+ val bitmap = makeHardwareBitmap(100, 100)
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
- bitmapBundle, bounds, Insets.NONE, taskId, userId, topComponent)
+ bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
- val onSavedListener = mock<Consumer<Uri>>()
- val callback = mock<RequestCallback>()
+ val processedRequest = processor.process(request)
- processor.processRequest(request, onSavedListener, callback)
+ // No changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
+
+ @Test
+ fun testProvidedImageScreenshot() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
- verify(controller).handleImageAsScreenshot(
- bitmapCaptor.capture(), eq(bounds), eq(Insets.NONE), eq(taskId), eq(userId),
- eq(topComponent), eq(onSavedListener), eq(callback)
- )
+ val bounds = Rect(50, 50, 150, 150)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ policy.setManagedProfile(USER_ID, false)
+
+ val bitmap = makeHardwareBitmap(100, 100)
+ val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- assertThat(bitmapCaptor.value.equalsHardwareBitmap(bitmap)).isTrue()
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+ bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+
+ val processedRequest = processor.process(request)
+
+ // No changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
+
+ @Test
+ fun testProvidedImageScreenshot_managedProfile() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ val bounds = Rect(50, 50, 150, 150)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ // Indicate that the screenshot belongs to a manged profile
+ policy.setManagedProfile(USER_ID, true)
+
+ val bitmap = makeHardwareBitmap(100, 100)
+ val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+ bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+
+ val processedRequest = processor.process(request)
+
+ // Work profile, but already a task snapshot, so no changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
+
+ private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
+ val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+ return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
}
- private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
- return bitmap.hardwareBuffer == this.hardwareBuffer &&
- bitmap.colorSpace == this.colorSpace
+ private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
+ val provided = bundleToHardwareBitmap(bundle)
+ return provided.hardwareBuffer == this.hardwareBuffer &&
+ provided.colorSpace == this.colorSpace
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 18acf3f6ce53..8f2b715ba051 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -63,6 +63,7 @@ import android.graphics.drawable.Icon;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.dreams.IDreamManager;
@@ -204,6 +205,8 @@ public class BubblesTest extends SysuiTestCase {
private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor;
@Captor
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
private BubblesManager mBubblesManager;
private TestableBubbleController mBubbleController;
@@ -240,6 +243,8 @@ public class BubblesTest extends SysuiTestCase {
@Mock
private IStatusBarService mStatusBarService;
@Mock
+ private IDreamManager mIDreamManager;
+ @Mock
private NotificationVisibilityProvider mVisibilityProvider;
@Mock
private LauncherApps mLauncherApps;
@@ -371,10 +376,11 @@ public class BubblesTest extends SysuiTestCase {
mContext,
mBubbleController.asBubbles(),
mNotificationShadeWindowController,
- mock(KeyguardStateController.class),
+ mKeyguardStateController,
mShadeController,
mStatusBarService,
mock(INotificationManager.class),
+ mIDreamManager,
mVisibilityProvider,
interruptionStateProvider,
mZenModeController,
@@ -391,6 +397,25 @@ public class BubblesTest extends SysuiTestCase {
verify(mNotifPipeline, atLeastOnce())
.addCollectionListener(mNotifListenerCaptor.capture());
mEntryListener = mNotifListenerCaptor.getValue();
+
+ // Get a reference to KeyguardStateController.Callback
+ verify(mKeyguardStateController, atLeastOnce())
+ .addCallback(mKeyguardStateControllerCallbackCaptor.capture());
+ }
+
+ @Test
+ public void dreamingHidesBubbles() throws RemoteException {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+ assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE);
+
+ when(mIDreamManager.isDreamingOrInPreview()).thenReturn(true); // dreaming is happening
+ when(mKeyguardStateController.isShowing()).thenReturn(false); // device is unlocked
+ KeyguardStateController.Callback callback =
+ mKeyguardStateControllerCallbackCaptor.getValue();
+ callback.onKeyguardShowingChanged();
+
+ assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b97aa5f83347..aa2a25b9cdd3 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6684,6 +6684,20 @@ public class NotificationManagerService extends SystemService {
}
}
+ // Ensure only allowed packages have a substitute app name
+ if (notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
+ int hasSubstituteAppNamePermission = mPackageManager.checkPermission(
+ permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg, userId);
+ if (hasSubstituteAppNamePermission != PERMISSION_GRANTED) {
+ notification.extras.remove(Notification.EXTRA_SUBSTITUTE_APP_NAME);
+ if (DBG) {
+ Slog.w(TAG, "warning: pkg " + pkg + " attempting to substitute app name"
+ + " without holding perm "
+ + Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
+ }
+ }
+ }
+
// Remote views? Are they too big?
checkRemoteViews(pkg, tag, id, notification);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 025e97318ba9..87ab87df0b0c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DISALLOW_USER_SWITCH;
import android.Manifest;
import android.accounts.Account;
@@ -1885,6 +1886,19 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
+ public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
+ boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.USER_SWITCHER_ENABLED,
+ Resources.getSystem().getBoolean(com.android.internal
+ .R.bool.config_showUserSwitcherByDefault) ? 1 : 0) != 0;
+
+ return UserManager.supportsMultipleUsers()
+ && !hasUserRestriction(DISALLOW_USER_SWITCH, mUserId)
+ && !UserManager.isDeviceInDemoMode(mContext)
+ && multiUserSettingOn;
+ }
+
+ @Override
public boolean isRestricted(@UserIdInt int userId) {
if (userId != UserHandle.getCallingUserId()) {
checkCreateUsersPermission("query isRestricted for user " + userId);
@@ -2277,7 +2291,6 @@ public class UserManagerService extends IUserManager.Stub {
originatingUserId, local);
localChanged = updateLocalRestrictionsForTargetUsersLR(originatingUserId, local,
updatedLocalTargetUserIds);
-
if (isDeviceOwner) {
// Remember the global restriction owner userId to be able to make a distinction
// in getUserRestrictionSource on who set local policies.
@@ -4804,41 +4817,59 @@ public class UserManagerService extends IUserManager.Stub {
null, // use default PullAtomMetadata values
BackgroundThread.getExecutor(),
this::onPullAtom);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.MULTI_USER_INFO,
+ null, // use default PullAtomMetadata values
+ BackgroundThread.getExecutor(),
+ this::onPullAtom);
}
/** Writes a UserInfo pulled atom for each user on the device. */
private int onPullAtom(int atomTag, List<StatsEvent> data) {
- if (atomTag != FrameworkStatsLog.USER_INFO) {
- Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag);
- return android.app.StatsManager.PULL_SKIP;
- }
- final List<UserInfo> users = getUsersInternal(true, true, true);
- final int size = users.size();
- if (size > 1) {
- for (int idx = 0; idx < size; idx++) {
- final UserInfo user = users.get(idx);
- final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
- final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
- .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
- ?
- user.userType : null;
-
- boolean isUserRunningUnlocked;
- synchronized (mUserStates) {
- isUserRunningUnlocked =
- mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
+ if (atomTag == FrameworkStatsLog.USER_INFO) {
+ final List<UserInfo> users = getUsersInternal(true, true, true);
+ final int size = users.size();
+ if (size > 1) {
+ for (int idx = 0; idx < size; idx++) {
+ final UserInfo user = users.get(idx);
+ final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
+ final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
+ .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
+ ?
+ user.userType : null;
+
+ boolean isUserRunningUnlocked;
+ synchronized (mUserStates) {
+ isUserRunningUnlocked =
+ mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
+ }
+
+ data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
+ user.id,
+ userTypeStandard,
+ userTypeCustom,
+ user.flags,
+ user.creationTime,
+ user.lastLoggedInTime,
+ isUserRunningUnlocked
+ ));
+ }
+ }
+ } else if (atomTag == FrameworkStatsLog.MULTI_USER_INFO) {
+ if (UserManager.getMaxSupportedUsers() > 1) {
+ int deviceOwnerUserId = UserHandle.USER_NULL;
+
+ synchronized (mRestrictionsLock) {
+ deviceOwnerUserId = mDeviceOwnerUserId;
}
- data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
- user.id,
- userTypeStandard,
- userTypeCustom,
- user.flags,
- user.creationTime,
- user.lastLoggedInTime,
- isUserRunningUnlocked
- ));
+ data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.MULTI_USER_INFO,
+ UserManager.getMaxSupportedUsers(),
+ isUserSwitcherEnabled(deviceOwnerUserId)));
}
+ } else {
+ Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag);
+ return android.app.StatsManager.PULL_SKIP;
}
return android.app.StatsManager.PULL_SUCCESS;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 51bf55759962..7dcfd8bd446b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2082,21 +2082,22 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
- return handleTransitionForKeyguardLw(false /* startKeyguardExitAnimation */,
- false /* notifyOccluded */);
+ // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
+ // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
+ // need to call IKeyguardService#keyguardGoingAway here.
+ return handleStartTransitionForKeyguardLw(keyguardGoingAway
+ && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation,
+ keyguardOccluding, duration);
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
- boolean keyguardOccludedCancelled) {
- // When app KEYGUARD_GOING_AWAY or (UN)OCCLUDE app transition is canceled, we need
- // to trigger relevant IKeyguardService calls to sync keyguard status in
- // WindowManagerService and SysUI.
- handleTransitionForKeyguardLw(
- keyguardGoingAwayCancelled /* startKeyguardExitAnimation */,
- keyguardOccludedCancelled /* notifyOccluded */);
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+ handleStartTransitionForKeyguardLw(
+ keyguardGoingAway, false /* keyguardOccludingStarted */,
+ 0 /* duration */);
}
});
@@ -3262,39 +3263,31 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mPendingKeyguardOccluded = occluded;
mKeyguardOccludedChanged = true;
} else {
- setKeyguardOccludedLw(occluded, true /* notify */);
+ setKeyguardOccludedLw(occluded, false /* force */,
+ false /* transitionStarted */);
}
}
@Override
- public int applyKeyguardOcclusionChange(boolean notify) {
+ public int applyKeyguardOcclusionChange(boolean transitionStarted) {
if (mKeyguardOccludedChanged) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
+ mPendingKeyguardOccluded);
- if (setKeyguardOccludedLw(mPendingKeyguardOccluded, notify)) {
+ if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */,
+ transitionStarted)) {
return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
}
}
return 0;
}
- /**
- * Called when keyguard related app transition starts, or cancelled.
- *
- * @param startKeyguardExitAnimation Trigger IKeyguardService#startKeyguardExitAnimation to
- * start keyguard exit animation.
- * @param notifyOccluded Trigger IKeyguardService#setOccluded binder call to notify whether
- * the top activity can occlude the keyguard or not.
- *
- * @return Whether the flags have changed and we have to redo the layout.
- */
- private int handleTransitionForKeyguardLw(boolean startKeyguardExitAnimation,
- boolean notifyOccluded) {
- final int redoLayout = applyKeyguardOcclusionChange(notifyOccluded);
+ private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration) {
+ final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding);
if (redoLayout != 0) return redoLayout;
- if (startKeyguardExitAnimation) {
+ if (keyguardGoingAway) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
- startKeyguardExitAnimation(SystemClock.uptimeMillis());
+ startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
}
return 0;
}
@@ -3526,18 +3519,28 @@ public class PhoneWindowManager implements WindowManagerPolicy {
* Updates the occluded state of the Keyguard.
*
* @param isOccluded Whether the Keyguard is occluded by another window.
- * @param notify Notify keyguard occlude status change immediately via
- * {@link com.android.internal.policy.IKeyguardService}.
+ * @param force notify the occluded status to KeyguardService and update flags even though
+ * occlude status doesn't change.
+ * @param transitionStarted {@code true} if keyguard (un)occluded transition started.
* @return Whether the flags have changed and we have to redo the layout.
*/
- private boolean setKeyguardOccludedLw(boolean isOccluded, boolean notify) {
+ private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force,
+ boolean transitionStarted) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
mKeyguardOccludedChanged = false;
- if (isKeyguardOccluded() == isOccluded) {
+ if (isKeyguardOccluded() == isOccluded && !force) {
return false;
}
- mKeyguardDelegate.setOccluded(isOccluded, notify);
- return mKeyguardDelegate.isShowing();
+
+ final boolean showing = mKeyguardDelegate.isShowing();
+ final boolean animate = showing && !isOccluded;
+ // When remote animation is enabled for keyguard (un)occlude transition, KeyguardService
+ // uses remote animation start as a signal to update its occlusion status ,so we don't need
+ // to notify here.
+ final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
+ || !transitionStarted;
+ mKeyguardDelegate.setOccluded(isOccluded, animate, notify);
+ return showing;
}
/** {@inheritDoc} */
@@ -4933,10 +4936,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
- public void startKeyguardExitAnimation(long startTime) {
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
if (mKeyguardDelegate != null) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.startKeyguardExitAnimation");
- mKeyguardDelegate.startKeyguardExitAnimation(startTime);
+ mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration);
}
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 2b0405073323..4f00992c713e 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -171,10 +171,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
void onKeyguardOccludedChangedLw(boolean occluded);
/**
- * @param notify {@code true} if the status change should be immediately notified via
- * {@link com.android.internal.policy.IKeyguardService}
+ * Applies a keyguard occlusion change if one happened.
+ * @param transitionStarted Whether keyguard (un)occlude transition is starting or not.
*/
- int applyKeyguardOcclusionChange(boolean notify);
+ int applyKeyguardOcclusionChange(boolean transitionStarted);
/**
* Interface to the Window Manager state associated with a particular
@@ -1129,10 +1129,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
/**
* Notifies the keyguard to start fading out.
- * @param startTime the start time of the animation in uptime milliseconds
*
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
- void startKeyguardExitAnimation(long startTime);
+ void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
/**
* Called when System UI has been started.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 7737421654ee..b79ac6f68be2 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -249,10 +249,10 @@ public class KeyguardServiceDelegate {
}
}
- public void setOccluded(boolean isOccluded, boolean notify) {
+ public void setOccluded(boolean isOccluded, boolean animate, boolean notify) {
if (mKeyguardService != null && notify) {
- if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ")");
- mKeyguardService.setOccluded(isOccluded, false /* animate */);
+ if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
+ mKeyguardService.setOccluded(isOccluded, animate);
}
mKeyguardState.occluded = isOccluded;
}
@@ -394,9 +394,9 @@ public class KeyguardServiceDelegate {
}
}
- public void startKeyguardExitAnimation(long startTime) {
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
if (mKeyguardService != null) {
- mKeyguardService.startKeyguardExitAnimation(startTime, 0);
+ mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0a4bc60d7ece..de3b2a686dde 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1486,6 +1486,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ a.configChanges = ActivityInfo.CONFIG_ORIENTATION;
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 5a1afc49c62b..20032d668344 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -789,7 +789,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// schedule launch ticks to collect information about slow apps.
r.startLaunchTickingLocked();
-
+ r.lastLaunchTime = SystemClock.uptimeMillis();
r.setProcess(proc);
// Ensure activity is allowed to be resumed after process has set.
@@ -835,8 +835,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
final IActivityClientController activityClientController =
proc.hasEverLaunchedActivity() ? null : mService.mActivityClientController;
r.launchCount++;
- r.lastLaunchTime = SystemClock.uptimeMillis();
- proc.setLastActivityLaunchTime(r.lastLaunchTime);
if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 95169dbd7092..55d6b2fe8226 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -375,6 +375,9 @@ public class AppTransition implements Dump {
final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
int redoLayout = notifyAppTransitionStartingLocked(
+ AppTransition.isKeyguardGoingAwayTransitOld(transit),
+ AppTransition.isKeyguardOccludeTransitOld(transit),
+ topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
? topOpeningAnim.getStatusBarTransitionsStartTime()
: SystemClock.uptimeMillis(),
@@ -413,11 +416,8 @@ public class AppTransition implements Dump {
}
void freeze() {
- final boolean keyguardGoingAwayCancelled = mNextAppTransitionRequests.contains(
+ final boolean keyguardGoingAway = mNextAppTransitionRequests.contains(
TRANSIT_KEYGUARD_GOING_AWAY);
- final boolean keyguardOccludedCancelled =
- mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_OCCLUDE)
- || mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_UNOCCLUDE);
// The RemoteAnimationControl didn't register AppTransitionListener and
// only initialized the finish and timeout callback when goodToGo().
@@ -429,7 +429,7 @@ public class AppTransition implements Dump {
mNextAppTransitionRequests.clear();
clear();
setReady();
- notifyAppTransitionCancelledLocked(keyguardGoingAwayCancelled, keyguardOccludedCancelled);
+ notifyAppTransitionCancelledLocked(keyguardGoingAway);
}
private void setAppTransitionState(int state) {
@@ -479,11 +479,9 @@ public class AppTransition implements Dump {
}
}
- private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
- boolean keyguardOccludedCancelled) {
+ private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAway) {
for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAwayCancelled,
- keyguardOccludedCancelled);
+ mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAway);
}
}
@@ -493,12 +491,14 @@ public class AppTransition implements Dump {
}
}
- private int notifyAppTransitionStartingLocked(long statusBarAnimationStartTime,
+ private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOcclude, long duration, long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
int redoLayout = 0;
for (int i = 0; i < mListeners.size(); i++) {
- redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(
- statusBarAnimationStartTime, statusBarAnimationDuration);
+ redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
+ keyguardOcclude, duration, statusBarAnimationStartTime,
+ statusBarAnimationDuration);
}
return redoLayout;
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 4b0005d77e40..44f388b6ed39 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -20,6 +20,10 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
@@ -91,6 +95,7 @@ import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionFlags;
import android.view.WindowManager.TransitionOldType;
import android.view.WindowManager.TransitionType;
+import android.view.animation.Animation;
import android.window.ITaskFragmentOrganizer;
import com.android.internal.annotations.VisibleForTesting;
@@ -292,6 +297,7 @@ public class AppTransitionController {
final int flags = appTransition.getTransitFlags();
layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
+ handleNonAppWindowsInTransition(transit, flags);
appTransition.postAnimationCallback();
appTransition.clear();
} finally {
@@ -1165,6 +1171,30 @@ public class AppTransitionController {
}
}
+ private void handleNonAppWindowsInTransition(@TransitionOldType int transit, int flags) {
+ if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+ if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+ && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
+ && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
+ Animation anim = mService.mPolicy.createKeyguardWallpaperExit(
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+ if (anim != null) {
+ anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+ mDisplayContent.mWallpaperController.startWallpaperAnimation(anim);
+ }
+ }
+ }
+ if ((transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER)
+ && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+ mDisplayContent.startKeyguardExitOnNonAppWindows(
+ transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
+ }
+ }
+
private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
ArrayMap<WindowContainer, Integer> outReasons) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c6fa4c1874be..8a34af3b0107 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6598,8 +6598,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
- boolean keyguardOccludedCancelled) {
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
// It is only needed when freezing display in legacy transition.
if (mTransitionController.isShellTransitionsEnabled()) return;
continueUpdateOrientationForDiffOrienLaunchingApp();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 24cef31dde7c..1a34c93f2ad6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -602,8 +602,9 @@ public class DisplayPolicy {
}
@Override
- public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
if (statusBar != null) {
@@ -615,8 +616,7 @@ public class DisplayPolicy {
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
- boolean keyguardOccludedCancelled) {
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
mHandler.post(mAppTransitionCancelled);
}
@@ -794,11 +794,11 @@ public class DisplayPolicy {
}
public void setAwake(boolean awake) {
- if (awake == mAwake) {
- return;
- }
- mAwake = awake;
- synchronized (mService.mGlobalLock) {
+ synchronized (mLock) {
+ if (awake == mAwake) {
+ return;
+ }
+ mAwake = awake;
if (!mDisplayContent.isDefaultDisplay) {
return;
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index db79eae1d80c..5b702eac7059 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -161,15 +161,15 @@ public class RecentsAnimationController implements DeathRecipient {
*/
final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
continueDeferredCancel();
return 0;
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
- boolean keyguardOccludedCancelled) {
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
continueDeferredCancel();
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 6e0d411caca4..803890b4032d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -31,7 +31,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -66,6 +72,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -73,6 +80,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.view.animation.Animation;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -1058,9 +1066,36 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
@TransitionType int transit, @TransitionFlags int flags) {
+ if ((transit == TRANSIT_KEYGUARD_GOING_AWAY
+ || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
+ && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+ if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+ && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
+ && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
+ Animation anim = mController.mAtm.mWindowManager.mPolicy
+ .createKeyguardWallpaperExit(
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+ if (anim != null) {
+ anim.scaleCurrentDuration(
+ mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
+ dc.mWallpaperController.startWallpaperAnimation(anim);
+ }
+ }
+ dc.startKeyguardExitOnNonAppWindows(
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
+ if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+ // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
+ // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
+ // need to call IKeyguardService#keyguardGoingAway here.
+ mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
+ SystemClock.uptimeMillis(), 0 /* duration */);
+ }
+ }
if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(
- false /* notify */);
+ true /* keyguardOccludingStarted */);
}
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 010c509ddd8a..846aa3e3739a 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -637,9 +637,11 @@ class TransitionController {
}
void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
+ final boolean keyguardGoingAway = info.isKeyguardGoingAway();
for (int i = 0; i < mLegacyListeners.size(); ++i) {
// TODO(shell-transitions): handle (un)occlude transition.
- mLegacyListeners.get(i).onAppTransitionStartingLocked(
+ mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
+ false /* keyguardOcclude */, 0 /* durationHint */,
SystemClock.uptimeMillis() + statusBarTransitionDelay,
AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
}
@@ -654,7 +656,7 @@ class TransitionController {
void dispatchLegacyAppTransitionCancelled() {
for (int i = 0; i < mLegacyListeners.size(); ++i) {
mLegacyListeners.get(i).onAppTransitionCancelledLocked(
- false /* keyguardGoingAwayCancelled */, false /* keyguardOccludedCancelled */);
+ false /* keyguardGoingAway */);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 4b5b6dad4fc4..a71c3866ba38 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -220,13 +220,9 @@ public abstract class WindowManagerInternal {
/**
* Called when a pending app transition gets cancelled.
*
- * @param keyguardGoingAwayCancelled {@code true} if keyguard going away transition was
- * cancelled.
- * @param keyguardOccludedCancelled {@code true} if keyguard (un)occluded transition was
- * cancelled.
+ * @param keyguardGoingAway true if keyguard going away transition got cancelled.
*/
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
- boolean keyguardOccludedCancelled) {}
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {}
/**
* Called when an app transition is timed out.
@@ -236,6 +232,9 @@ public abstract class WindowManagerInternal {
/**
* Called when an app transition gets started
*
+ * @param keyguardGoingAway true if keyguard going away transition is started.
+ * @param keyguardOccluding true if keyguard (un)occlude transition is started.
+ * @param duration the total duration of the transition
* @param statusBarAnimationStartTime the desired start time for all visual animations in
* the status bar caused by this app transition in uptime millis
* @param statusBarAnimationDuration the duration for all visual animations in the status
@@ -246,7 +245,8 @@ public abstract class WindowManagerInternal {
* {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
* or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
*/
- public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 298f93d75e7a..22411bb068a0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -425,6 +425,32 @@ public class WindowManagerService extends IWindowManager.Stub
SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false);
/**
+ * Run Keyguard animation as remote animation in System UI instead of local animation in
+ * the server process.
+ *
+ * 0: Runs all keyguard animation as local animation
+ * 1: Only runs keyguard going away animation as remote animation
+ * 2: Runs all keyguard animation as remote animation
+ */
+ private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
+ "persist.wm.enable_remote_keyguard_animation";
+
+ private static final int sEnableRemoteKeyguardAnimation =
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
+
+ /**
+ * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
+ */
+ public static final boolean sEnableRemoteKeyguardGoingAwayAnimation =
+ sEnableRemoteKeyguardAnimation >= 1;
+
+ /**
+ * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
+ */
+ public static final boolean sEnableRemoteKeyguardOccludeAnimation =
+ sEnableRemoteKeyguardAnimation >= 2;
+
+ /**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
* when the display has different orientation.
*/
@@ -1092,8 +1118,7 @@ public class WindowManagerService extends IWindowManager.Stub
= new WindowManagerInternal.AppTransitionListener() {
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
- boolean keyguardOccludedCancelled) {
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 34b40c716b4f..b1ad8ec1cb66 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -16,53 +16,79 @@
package com.android.server.pm;
+import static android.os.UserManager.DISALLOW_USER_SWITCH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.FileUtils;
+import android.os.Looper;
import android.os.Parcelable;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
import android.support.test.uiautomator.UiDevice;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
+/** Test {@link UserManagerService} functionality. */
@Postsubmit
-@SmallTest
-public class UserManagerServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class UserManagerServiceTest {
private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["};
private File restrictionsFile;
private int tempUserId = UserHandle.USER_NULL;
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private UserManagerService mUserManagerService;
+
+ @Before
+ public void setup() throws Exception {
+ // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+ // TODO: Remove once UMS supports proper dependency injection
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ // Disable binder caches in this process.
+ PropertyInvalidatedCache.disableForTestMode();
+
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
- @Override
- protected void setUp() throws Exception {
- super.setUp();
restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
restrictionsFile.delete();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void teardown() throws Exception {
restrictionsFile.delete();
if (tempUserId != UserHandle.USER_NULL) {
UserManager.get(mContext).removeUser(tempUserId);
}
- super.tearDown();
}
+ @Test
public void testWriteReadApplicationRestrictions() throws IOException {
AtomicFile atomicFile = new AtomicFile(restrictionsFile);
Bundle bundle = createBundle();
UserManagerService.writeApplicationRestrictionsLAr(bundle, atomicFile);
- assertTrue(atomicFile.getBaseFile().exists());
+ assertThat(atomicFile.getBaseFile().exists()).isTrue();
String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
System.out.println("restrictionsFile: " + s);
bundle = UserManagerService.readApplicationRestrictionsLAr(atomicFile);
@@ -70,22 +96,22 @@ public class UserManagerServiceTest extends AndroidTestCase {
assertBundle(bundle);
}
+ @Test
public void testAddUserWithAccount() {
UserManager um = UserManager.get(mContext);
UserInfo user = um.createUser("Test User", 0);
- assertNotNull(user);
+ assertThat(user).isNotNull();
tempUserId = user.id;
String accountName = "Test Account";
um.setUserAccount(tempUserId, accountName);
- assertEquals(accountName, um.getUserAccount(tempUserId));
+ assertThat(um.getUserAccount(tempUserId)).isEqualTo(accountName);
}
+ @Test
public void testUserSystemPackageWhitelist() throws Exception {
String cmd = "cmd user report-system-user-package-whitelist-problems --critical-only";
final String result = runShellCommand(cmd);
- if (!TextUtils.isEmpty(result)) {
- fail("Command '" + cmd + " reported errors:\n" + result);
- }
+ assertThat(result).isEmpty();
}
private Bundle createBundle() {
@@ -114,26 +140,141 @@ public class UserManagerServiceTest extends AndroidTestCase {
}
private void assertBundle(Bundle bundle) {
- assertFalse(bundle.getBoolean("boolean_0"));
- assertTrue(bundle.getBoolean("boolean_1"));
- assertEquals(100, bundle.getInt("integer"));
- assertEquals("", bundle.getString("empty"));
- assertEquals("text", bundle.getString("string"));
- assertEquals(Arrays.asList(STRING_ARRAY), Arrays.asList(bundle.getStringArray("string[]")));
+ assertThat(bundle.getBoolean("boolean_0")).isFalse();
+ assertThat(bundle.getBoolean("boolean_1")).isTrue();
+ assertThat(bundle.getInt("integer")).isEqualTo(100);
+ assertThat(bundle.getString("empty")).isEqualTo("");
+ assertThat(bundle.getString("string")).isEqualTo("text");
+ assertThat(Arrays.asList(bundle.getStringArray("string[]")))
+ .isEqualTo(Arrays.asList(STRING_ARRAY));
Parcelable[] bundle_array = bundle.getParcelableArray("bundle_array");
- assertEquals(2, bundle_array.length);
+ assertThat(bundle_array.length).isEqualTo(2);
Bundle bundle1 = (Bundle) bundle_array[0];
- assertEquals("bundle_array_string", bundle1.getString("bundle_array_string"));
- assertNotNull(bundle1.getBundle("bundle_array_bundle"));
+ assertThat(bundle1.getString("bundle_array_string"))
+ .isEqualTo("bundle_array_string");
+ assertThat(bundle1.getBundle("bundle_array_bundle")).isNotNull();
Bundle bundle2 = (Bundle) bundle_array[1];
- assertEquals("bundle_array_string2", bundle2.getString("bundle_array_string2"));
+ assertThat(bundle2.getString("bundle_array_string2"))
+ .isEqualTo("bundle_array_string2");
Bundle childBundle = bundle.getBundle("bundle");
- assertEquals("bundle_string", childBundle.getString("bundle_string"));
- assertEquals(1, childBundle.getInt("bundle_int"));
+ assertThat(childBundle.getString("bundle_string"))
+ .isEqualTo("bundle_string");
+ assertThat(childBundle.getInt("bundle_int")).isEqualTo(1);
+ }
+
+ @Test
+ public void assertHasUserRestriction() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId);
+ assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isTrue();
+
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+ assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isFalse();
+ }
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnMultiUserSettings() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ resetUserSwitcherEnabled();
+
+ setUserSwitch(false);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ setUserSwitch(true);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnMaxSupportedUsers() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ setMaxSupportedUsers(1);
+
+ assertThat(UserManager.supportsMultipleUsers()).isFalse();
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ setMaxSupportedUsers(8);
+
+ assertThat(UserManager.supportsMultipleUsers()).isTrue();
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnShowMultiuserUI() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ setShowMultiuserUI(false);
+
+ assertThat(UserManager.supportsMultipleUsers()).isFalse();
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ setShowMultiuserUI(true);
+
+ assertThat(UserManager.supportsMultipleUsers()).isTrue();
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnUserRestrictions() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ resetUserSwitcherEnabled();
+
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnDemoMode() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ resetUserSwitcherEnabled();
+
+ setDeviceDemoMode(true);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ setDeviceDemoMode(false);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+ private void resetUserSwitcherEnabled() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ setUserSwitch(true);
+ setShowMultiuserUI(true);
+ setDeviceDemoMode(false);
+ setMaxSupportedUsers(8);
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+ }
+
+ private void setUserSwitch(boolean enabled) {
+ android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.USER_SWITCHER_ENABLED, enabled ? 1 : 0);
}
+ private void setDeviceDemoMode(boolean enabled) {
+ android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.DEVICE_DEMO_MODE, enabled ? 1 : 0);
+ }
+
+
private static String runShellCommand(String cmd) throws Exception {
return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.executeShellCommand(cmd);
}
+
+ private static String setSystemProperty(String name, String value) throws Exception {
+ final String oldValue = runShellCommand("getprop " + name);
+ assertThat(runShellCommand("setprop " + name + " " + value))
+ .isEqualTo("");
+ return oldValue;
+ }
+
+ private static void setMaxSupportedUsers(int max) throws Exception {
+ setSystemProperty("fw.max_users", String.valueOf(max));
+ }
+
+ public static void setShowMultiuserUI(boolean show) throws Exception {
+ setSystemProperty("fw.show_multiuserui", String.valueOf(show));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 40cda34f1272..ec48e239d272 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -4239,6 +4239,59 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testSubstituteAppName_hasPermission() throws RemoteException {
+ String subName = "Substitute Name";
+ when(mPackageManager.checkPermission(
+ eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
+ .thenReturn(PERMISSION_GRANTED);
+ Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName);
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .addExtras(extras);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testSubstituteAppNamePermission", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ NotificationRecord posted = mService.findNotificationLocked(
+ PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+ assertTrue(posted.getNotification().extras
+ .containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME));
+ assertEquals(posted.getNotification().extras
+ .getString(Notification.EXTRA_SUBSTITUTE_APP_NAME), subName);
+ }
+
+ @Test
+ public void testSubstituteAppName_noPermission() throws RemoteException {
+ when(mPackageManager.checkPermission(
+ eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
+ .thenReturn(PERMISSION_DENIED);
+ Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Substitute Name");
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .addExtras(extras);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testSubstituteAppNamePermission", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ NotificationRecord posted = mService.findNotificationLocked(
+ PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+ assertFalse(posted.getNotification().extras
+ .containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME));
+ }
+
+ @Test
public void testGetNotificationCountLocked() {
String sampleTagToExclude = null;
int sampleIdToExclude = 0;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 88e58eab58aa..8546763aebec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -281,7 +281,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
// Simulate the app transition finishing
- mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0);
+ mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0);
verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 13da1543cfb8..d2cb7ba5d311 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -310,11 +310,11 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public void startKeyguardExitAnimation(long startTime) {
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
}
@Override
- public int applyKeyguardOcclusionChange(boolean notify) {
+ public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) {
return 0;
}
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
index 3b201f9d20dd..e4add8098105 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
@@ -16,6 +16,9 @@
package android.net.vcn.persistablebundleutils;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.isIkeOptionValid;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static android.telephony.TelephonyManager.APPTYPE_USIM;
@@ -134,15 +137,37 @@ public class IkeSessionParamsUtilsTest {
verifyPersistableBundleEncodeDecodeIsLossless(params);
}
+ private static IkeSessionParams.Builder createBuilderMinimumWithEap() throws Exception {
+ final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
+
+ final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII);
+ final int subId = 1;
+ final EapSessionConfig eapConfig =
+ new EapSessionConfig.Builder()
+ .setEapIdentity(eapId)
+ .setEapSimConfig(subId, APPTYPE_USIM)
+ .setEapAkaConfig(subId, APPTYPE_USIM)
+ .build();
+ return createBuilderMinimum().setAuthEap(serverCaCert, eapConfig);
+ }
+
@Test
public void testEncodeDecodeParamsWithIkeOptions() throws Exception {
- final IkeSessionParams params =
- createBuilderMinimum()
+ final IkeSessionParams.Builder builder =
+ createBuilderMinimumWithEap()
.addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID)
+ .addIkeOption(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH)
.addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
+ .addIkeOption(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500)
.addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT)
- .build();
- verifyPersistableBundleEncodeDecodeIsLossless(params);
+ .addIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+ if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION)) {
+ builder.addIkeOption(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+ }
+ if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES)) {
+ builder.addIkeOption(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+ }
+ verifyPersistableBundleEncodeDecodeIsLossless(builder.build());
}
private static InputStream openAssetsFile(String fileName) throws Exception {
@@ -176,19 +201,7 @@ public class IkeSessionParamsUtilsTest {
@Test
public void testEncodeRecodeParamsWithEapAuth() throws Exception {
- final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
-
- final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII);
- final int subId = 1;
- final EapSessionConfig eapConfig =
- new EapSessionConfig.Builder()
- .setEapIdentity(eapId)
- .setEapSimConfig(subId, APPTYPE_USIM)
- .setEapAkaConfig(subId, APPTYPE_USIM)
- .build();
-
- final IkeSessionParams params =
- createBuilderMinimum().setAuthEap(serverCaCert, eapConfig).build();
+ final IkeSessionParams params = createBuilderMinimumWithEap().build();
verifyPersistableBundleEncodeDecodeIsLossless(params);
}
}