diff options
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 Binary files differindex 918e514f4c89..e9a1721fba2a 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar 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); } } |