diff options
69 files changed, 1765 insertions, 682 deletions
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp index 6a685a79cc33..a1575173ded6 100644 --- a/cmds/app_process/Android.bp +++ b/cmds/app_process/Android.bp @@ -64,8 +64,6 @@ cc_binary { "libwilhelm", ], - header_libs: ["bionic_libc_platform_headers"], - compile_multilib: "both", cflags: [ diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 815f9455471c..12083b6fe20b 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -15,7 +15,6 @@ #include <android-base/macros.h> #include <binder/IPCThreadState.h> -#include <bionic/pac.h> #include <hwbinder/IPCThreadState.h> #include <utils/Log.h> #include <cutils/memory.h> @@ -183,10 +182,6 @@ int main(int argc, char* const argv[]) ALOGV("app_process main with argv: %s", argv_String.string()); } - // Because of applications that are using PAC instructions incorrectly, PAC - // is disabled in application processes for now. - ScopedDisablePAC x; - AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); // Process command line arguments // ignore argv[0] diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index f72288c670d9..9a7a949e3cd2 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -55,4 +55,5 @@ interface IPackageInstallerSession { int getParentSessionId(); boolean isStaged(); + int getInstallFlags(); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 3f8aedb31ea9..4030708d6a53 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1432,6 +1432,18 @@ public class PackageInstaller { } /** + * @return Session's {@link SessionParams#installFlags}. + * @hide + */ + public int getInstallFlags() { + try { + return mSession.getInstallFlags(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @return the session ID of the multi-package session that this belongs to or * {@link SessionInfo#INVALID_ID} if it does not belong to a multi-package session. */ diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl index 539fd4adb0a0..e1ccc4fb740b 100644 --- a/core/java/android/nfc/INfcTag.aidl +++ b/core/java/android/nfc/INfcTag.aidl @@ -45,4 +45,7 @@ interface INfcTag boolean canMakeReadOnly(int ndefType); int getMaxTransceiveLength(int technology); boolean getExtendedLengthApdusSupported(); + + void setTagUpToDate(long cookie); + boolean isTagUpToDate(long cookie); } diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java index 398ec63a931b..731d1ba78299 100644 --- a/core/java/android/nfc/Tag.java +++ b/core/java/android/nfc/Tag.java @@ -34,6 +34,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import android.os.SystemClock; import java.io.IOException; import java.util.Arrays; @@ -121,6 +122,7 @@ public final class Tag implements Parcelable { final INfcTag mTagService; // interface to NFC service, will be null if mock tag int mConnectedTechnology; + long mCookie; /** * Hidden constructor to be used by NFC service and internal classes. @@ -140,6 +142,17 @@ public final class Tag implements Parcelable { mTagService = tagService; mConnectedTechnology = -1; + mCookie = SystemClock.elapsedRealtime(); + + if (tagService == null) { + return; + } + + try { + tagService.setTagUpToDate(mCookie); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /** @@ -361,6 +374,22 @@ public final class Tag implements Parcelable { /** @hide */ @UnsupportedAppUsage public INfcTag getTagService() { + if (mTagService == null) { + return null; + } + + try { + if (!mTagService.isTagUpToDate(mCookie)) { + String id_str = ""; + for (int i = 0; i < mId.length; i++) { + id_str = id_str + String.format("%02X ", mId[i]); + } + String msg = "Permission Denial: Tag ( ID: " + id_str + ") is out of date"; + throw new SecurityException(msg); + } + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } return mTagService; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f1eb783726db..0b19f4f44653 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3390,6 +3390,12 @@ public final class ViewRootImpl implements ViewParent, mReportNextDraw = false; pendingDrawFinished(); } + + // Make sure the consumer is not waiting if the view root was just made invisible. + if (mBLASTDrawConsumer != null) { + mBLASTDrawConsumer.accept(null); + mBLASTDrawConsumer = null; + } } } diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl index 419b1f8feac7..d69a240b140b 100644 --- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl +++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl @@ -16,7 +16,7 @@ package com.android.internal.policy; interface IKeyguardStateCallback { - void onShowingStateChanged(boolean showing); + void onShowingStateChanged(boolean showing, int userId); void onSimSecureStateChanged(boolean simSecure); void onInputRestrictedStateChanged(boolean inputRestricted); void onTrustedChanged(boolean trusted); diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index e6deada45fc1..a7d78eb02ed1 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -66,7 +66,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.Consumer; /** * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal @@ -76,8 +75,6 @@ import java.util.function.Consumer; public class ConversationLayout extends FrameLayout implements ImageMessageConsumer, IMessagingLayout { - private static final Consumer<MessagingMessage> REMOVE_MESSAGE - = MessagingMessage::removeMessage; public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); @@ -150,6 +147,7 @@ public class ConversationLayout extends FrameLayout private Icon mShortcutIcon; private View mAppNameDivider; private TouchDelegateComposite mTouchDelegate = new TouchDelegateComposite(this); + private ArrayList<MessagingLinearLayout.MessagingChild> mToRecycle = new ArrayList<>(); public ConversationLayout(@NonNull Context context) { super(context); @@ -462,8 +460,12 @@ public class ConversationLayout extends FrameLayout removeGroups(oldGroups); // Let's remove the remaining messages - mMessages.forEach(REMOVE_MESSAGE); - mHistoricMessages.forEach(REMOVE_MESSAGE); + for (MessagingMessage message : mMessages) { + message.removeMessage(mToRecycle); + } + for (MessagingMessage historicMessage : mHistoricMessages) { + historicMessage.removeMessage(mToRecycle); + } mMessages = messages; mHistoricMessages = historicMessages; @@ -472,6 +474,12 @@ public class ConversationLayout extends FrameLayout updateTitleAndNamesDisplay(); updateConversationLayout(); + + // Recycle everything at the end of the update, now that we know it's no longer needed. + for (MessagingLinearLayout.MessagingChild child : mToRecycle) { + child.recycle(); + } + mToRecycle.clear(); } /** @@ -745,18 +753,18 @@ public class ConversationLayout extends FrameLayout MessagingGroup group = oldGroups.get(i); if (!mGroups.contains(group)) { List<MessagingMessage> messages = group.getMessages(); - Runnable endRunnable = () -> { - mMessagingLinearLayout.removeTransientView(group); - group.recycle(); - }; - boolean wasShown = group.isShown(); mMessagingLinearLayout.removeView(group); if (wasShown && !MessagingLinearLayout.isGone(group)) { mMessagingLinearLayout.addTransientView(group, 0); - group.removeGroupAnimated(endRunnable); + group.removeGroupAnimated(() -> { + mMessagingLinearLayout.removeTransientView(group); + group.recycle(); + }); } else { - endRunnable.run(); + // Defer recycling until after the update is done, since we may still need the + // old group around to perform other updates. + mToRecycle.add(group); } mMessages.removeAll(messages); mHistoricMessages.removeAll(messages); diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index f30b8442dc35..eaa9bcd72b84 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -263,7 +263,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return createdGroup; } - public void removeMessage(MessagingMessage messagingMessage) { + public void removeMessage(MessagingMessage messagingMessage, + ArrayList<MessagingLinearLayout.MessagingChild> toRecycle) { View view = messagingMessage.getView(); boolean wasShown = view.isShown(); ViewGroup messageParent = (ViewGroup) view.getParent(); @@ -271,15 +272,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return; } messageParent.removeView(view); - Runnable recycleRunnable = () -> { - messageParent.removeTransientView(view); - messagingMessage.recycle(); - }; if (wasShown && !MessagingLinearLayout.isGone(view)) { messageParent.addTransientView(view, 0); - performRemoveAnimation(view, recycleRunnable); + performRemoveAnimation(view, () -> { + messageParent.removeTransientView(view); + messagingMessage.recycle(); + }); } else { - recycleRunnable.run(); + toRecycle.add(messagingMessage); } } diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index e1602a981920..914de9e5e643 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -51,7 +51,6 @@ import com.android.internal.util.ContrastColorUtil; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.function.Consumer; /** * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal @@ -62,8 +61,6 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer, IMessagingLayout { private static final float COLOR_SHIFT_AMOUNT = 60; - private static final Consumer<MessagingMessage> REMOVE_MESSAGE - = MessagingMessage::removeMessage; public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); @@ -89,6 +86,7 @@ public class MessagingLayout extends FrameLayout private boolean mIsCollapsed; private ImageResolver mImageResolver; private CharSequence mConversationTitle; + private ArrayList<MessagingLinearLayout.MessagingChild> mToRecycle = new ArrayList<>(); public MessagingLayout(@NonNull Context context) { super(context); @@ -212,8 +210,12 @@ public class MessagingLayout extends FrameLayout removeGroups(oldGroups); // Let's remove the remaining messages - mMessages.forEach(REMOVE_MESSAGE); - mHistoricMessages.forEach(REMOVE_MESSAGE); + for (MessagingMessage message : mMessages) { + message.removeMessage(mToRecycle); + } + for (MessagingMessage historicMessage : mHistoricMessages) { + historicMessage.removeMessage(mToRecycle); + } mMessages = messages; mHistoricMessages = historicMessages; @@ -223,6 +225,12 @@ public class MessagingLayout extends FrameLayout // after groups are finalized, hide the first sender name if it's showing as the title mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, mConversationTitle); updateImageMessages(); + + // Recycle everything at the end of the update, now that we know it's no longer needed. + for (MessagingLinearLayout.MessagingChild child : mToRecycle) { + child.recycle(); + } + mToRecycle.clear(); } private void updateImageMessages() { @@ -263,18 +271,17 @@ public class MessagingLayout extends FrameLayout MessagingGroup group = oldGroups.get(i); if (!mGroups.contains(group)) { List<MessagingMessage> messages = group.getMessages(); - Runnable endRunnable = () -> { - mMessagingLinearLayout.removeTransientView(group); - group.recycle(); - }; boolean wasShown = group.isShown(); mMessagingLinearLayout.removeView(group); if (wasShown && !MessagingLinearLayout.isGone(group)) { mMessagingLinearLayout.addTransientView(group, 0); - group.removeGroupAnimated(endRunnable); + group.removeGroupAnimated(() -> { + mMessagingLinearLayout.removeTransientView(group); + group.recycle(); + }); } else { - endRunnable.run(); + mToRecycle.add(group); } mMessages.removeAll(messages); mHistoricMessages.removeAll(messages); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index cb1d387dbd07..c06f5f75514f 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -365,6 +365,7 @@ public class MessagingLinearLayout extends ViewGroup { default int getExtraSpacing() { return 0; } + void recycle(); } public static class LayoutParams extends MarginLayoutParams { diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java index 8c8437951402..2cc0d2305a78 100644 --- a/core/java/com/android/internal/widget/MessagingMessage.java +++ b/core/java/com/android/internal/widget/MessagingMessage.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.Notification; import android.view.View; +import java.util.ArrayList; import java.util.Objects; /** @@ -96,8 +97,8 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild { return sameAs(message.getMessage()); } - default void removeMessage() { - getGroup().removeMessage(this); + default void removeMessage(ArrayList<MessagingLinearLayout.MessagingChild> toRecycle) { + getGroup().removeMessage(this, toRecycle); } default void setMessagingGroup(MessagingGroup group) { diff --git a/data/etc/car/com.android.carsystemui.xml b/data/etc/car/com.android.carsystemui.xml index a267d5650782..6c9103cb0f4f 100644 --- a/data/etc/car/com.android.carsystemui.xml +++ b/data/etc/car/com.android.carsystemui.xml @@ -24,5 +24,6 @@ <permission name="android.car.permission.CONTROL_CAR_CLIMATE"/> <permission name="android.car.permission.CONTROL_CAR_EVS_ACTIVITY"/> <permission name="android.car.permission.MONITOR_CAR_EVS_STATUS"/> + <permission name="android.car.permission.CONTROL_CAR_APP_LAUNCH"/> </privapp-permissions> </permissions> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java index eb9429747b66..624d130e8698 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java @@ -18,17 +18,73 @@ package androidx.window.common; import static androidx.window.util.ExtensionHelper.isZero; +import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Rect; +import android.util.Log; import androidx.annotation.NonNull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ -final class CommonDisplayFeature implements DisplayFeature { +/** A representation of a folding feature for both Extension and Sidecar. + * For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and + * {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of + * {@link androidx.window.extensions.layout.FoldingFeature}. + */ +public final class CommonFoldingFeature { + + private static final boolean DEBUG = false; + + public static final String TAG = CommonFoldingFeature.class.getSimpleName(); + + /** + * A common type to represent a hinge where the screen is continuous. + */ + public static final int COMMON_TYPE_FOLD = 1; + + /** + * A common type to represent a hinge where there is a physical gap separating multiple + * displays. + */ + public static final int COMMON_TYPE_HINGE = 2; + + @IntDef({COMMON_TYPE_FOLD, COMMON_TYPE_HINGE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type { + } + + /** + * A common state to represent when the state is not known. One example is if the device is + * closed. We do not emit this value for developers but is useful for implementation reasons. + */ + public static final int COMMON_STATE_UNKNOWN = -1; + + /** + * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar + * and Extensions do not match exactly. + */ + public static final int COMMON_STATE_FLAT = 3; + /** + * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in + * Sidecar and Extensions do not match exactly. + */ + public static final int COMMON_STATE_HALF_OPENED = 2; + + /** + * The possible states for a folding hinge. + */ + @IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED}) + @Retention(RetentionPolicy.SOURCE) + public @interface State { + } + private static final Pattern FEATURE_PATTERN = Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?"); @@ -38,17 +94,49 @@ final class CommonDisplayFeature implements DisplayFeature { private static final String PATTERN_STATE_FLAT = "flat"; private static final String PATTERN_STATE_HALF_OPENED = "half-opened"; - // TODO(b/183049815): Support feature strings that include the state of the feature. + /** + * Parse a {@link List} of {@link CommonFoldingFeature} from a {@link String}. + * @param value a {@link String} representation of multiple {@link CommonFoldingFeature} + * separated by a ":". + * @param hingeState a global fallback value for a {@link CommonFoldingFeature} if one is not + * specified in the input. + * @throws IllegalArgumentException if the provided string is improperly formatted or could not + * otherwise be parsed. + * @see #FEATURE_PATTERN + * @return {@link List} of {@link CommonFoldingFeature}. + */ + static List<CommonFoldingFeature> parseListFromString(@NonNull String value, + @State int hingeState) { + List<CommonFoldingFeature> features = new ArrayList<>(); + String[] featureStrings = value.split(";"); + for (String featureString : featureStrings) { + CommonFoldingFeature feature; + try { + feature = CommonFoldingFeature.parseFromString(featureString, hingeState); + } catch (IllegalArgumentException e) { + if (DEBUG) { + Log.w(TAG, "Failed to parse display feature: " + featureString, e); + } + continue; + } + features.add(feature); + } + return features; + } /** * Parses a display feature from a string. * + * @param string A {@link String} representation of a {@link CommonFoldingFeature}. + * @param hingeState A fallback value for the {@link State} if it is not specified in the input. * @throws IllegalArgumentException if the provided string is improperly formatted or could not * otherwise be parsed. + * @return {@link CommonFoldingFeature} represented by the {@link String} value. * @see #FEATURE_PATTERN */ @NonNull - static CommonDisplayFeature parseFromString(@NonNull String string) { + private static CommonFoldingFeature parseFromString(@NonNull String string, + @State int hingeState) { Matcher featureMatcher = FEATURE_PATTERN.matcher(string); if (!featureMatcher.matches()) { throw new IllegalArgumentException("Malformed feature description format: " + string); @@ -59,10 +147,10 @@ final class CommonDisplayFeature implements DisplayFeature { int type; switch (featureType) { case FEATURE_TYPE_FOLD: - type = 1 /* TYPE_FOLD */; + type = COMMON_TYPE_FOLD; break; case FEATURE_TYPE_HINGE: - type = 2 /* TYPE_HINGE */; + type = COMMON_TYPE_HINGE; break; default: { throw new IllegalArgumentException("Malformed feature type: " + featureType); @@ -79,7 +167,7 @@ final class CommonDisplayFeature implements DisplayFeature { } String stateString = featureMatcher.group(6); stateString = stateString == null ? "" : stateString; - Integer state; + final int state; switch (stateString) { case PATTERN_STATE_FLAT: state = COMMON_STATE_FLAT; @@ -88,10 +176,10 @@ final class CommonDisplayFeature implements DisplayFeature { state = COMMON_STATE_HALF_OPENED; break; default: - state = null; + state = hingeState; break; } - return new CommonDisplayFeature(type, state, featureRect); + return new CommonFoldingFeature(type, state, featureRect); } catch (NumberFormatException e) { throw new IllegalArgumentException("Malformed feature description: " + string, e); } @@ -99,11 +187,11 @@ final class CommonDisplayFeature implements DisplayFeature { private final int mType; @Nullable - private final Integer mState; + private final int mState; @NonNull private final Rect mRect; - CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) { + CommonFoldingFeature(int type, int state, @NonNull Rect rect) { assertValidState(state); this.mType = type; this.mState = state; @@ -114,16 +202,19 @@ final class CommonDisplayFeature implements DisplayFeature { this.mRect = rect; } + /** Returns the type of the feature. */ + @Type public int getType() { return mType; } /** Returns the state of the feature, or {@code null} if the feature has no state. */ - @Nullable - public Integer getState() { + @State + public int getState() { return mState; } + /** Returns the bounds of the feature. */ @NonNull public Rect getRect() { return mRect; @@ -133,7 +224,7 @@ final class CommonDisplayFeature implements DisplayFeature { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - CommonDisplayFeature that = (CommonDisplayFeature) o; + CommonFoldingFeature that = (CommonFoldingFeature) o; return mType == that.mType && Objects.equals(mState, that.mState) && mRect.equals(that.mRect); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java index fa9a5a8b7a1b..6987401525b4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java @@ -18,11 +18,15 @@ package androidx.window.common; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; +import static androidx.window.common.CommonFoldingFeature.parseListFromString; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; +import android.text.TextUtils; import android.util.Log; import android.util.SparseIntArray; @@ -30,6 +34,7 @@ import androidx.window.util.BaseDataProducer; import com.android.internal.R; +import java.util.List; import java.util.Optional; /** @@ -37,10 +42,13 @@ import java.util.Optional; * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources * config at {@link R.array#config_device_state_postures}. */ -public final class DeviceStateManagerPostureProducer extends BaseDataProducer<Integer> { - private static final String TAG = "ConfigDevicePostureProducer"; +public final class DeviceStateManagerFoldingFeatureProducer extends + BaseDataProducer<List<CommonFoldingFeature>> { + private static final String TAG = + DeviceStateManagerFoldingFeatureProducer.class.getSimpleName(); private static final boolean DEBUG = false; + private final Context mContext; private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); private int mCurrentDeviceState = INVALID_DEVICE_STATE; @@ -50,7 +58,8 @@ public final class DeviceStateManagerPostureProducer extends BaseDataProducer<In notifyDataChanged(); }; - public DeviceStateManagerPostureProducer(@NonNull Context context) { + public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context) { + mContext = context; String[] deviceStatePosturePairs = context.getResources() .getStringArray(R.array.config_device_state_postures); for (String deviceStatePosturePair : deviceStatePosturePairs) { @@ -86,8 +95,17 @@ public final class DeviceStateManagerPostureProducer extends BaseDataProducer<In @Override @Nullable - public Optional<Integer> getData() { - final int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, -1); - return posture != -1 ? Optional.of(posture) : Optional.empty(); + public Optional<List<CommonFoldingFeature>> getData() { + final int globalHingeState = globalHingeState(); + String displayFeaturesString = mContext.getResources().getString( + R.string.config_display_features); + if (TextUtils.isEmpty(displayFeaturesString)) { + return Optional.empty(); + } + return Optional.of(parseListFromString(displayFeaturesString, globalHingeState)); + } + + private int globalHingeState() { + return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java deleted file mode 100644 index 573641857b99..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2021 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.common; - -import android.annotation.IntDef; -import android.annotation.Nullable; -import android.graphics.Rect; - -import androidx.annotation.NonNull; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ -public interface DisplayFeature { - /** Returns the type of the feature. */ - int getType(); - - /** Returns the state of the feature, or {@code null} if the feature has no state. */ - @Nullable - @State - Integer getState(); - - /** Returns the bounds of the feature. */ - @NonNull - Rect getRect(); - - /** - * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar - * and Extensions do not match exactly. - */ - int COMMON_STATE_FLAT = 3; - /** - * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in - * Sidecar and Extensions do not match exactly. - */ - int COMMON_STATE_HALF_OPENED = 2; - - /** - * The possible states for a folding hinge. - */ - @IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED}) - @Retention(RetentionPolicy.SOURCE) - @interface State {} - -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java new file mode 100644 index 000000000000..f2e403b4f792 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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.common; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; + +/** + * An empty implementation of {@link Application.ActivityLifecycleCallbacks} derived classes can + * implement the methods necessary. + */ +public class EmptyLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + } + + @Override + public void onActivityStarted(Activity activity) { + } + + @Override + public void onActivityResumed(Activity activity) { + } + + @Override + public void onActivityPaused(Activity activity) { + } + + @Override + public void onActivityStopped(Activity activity) { + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java deleted file mode 100644 index cd2cadc082e1..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2021 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.common; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; - -import androidx.window.util.BaseDataProducer; - -import com.android.internal.R; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * Implementation of {@link androidx.window.util.DataProducer} that produces - * {@link CommonDisplayFeature} parsed from a string stored in the resources config at - * {@link R.string#config_display_features}. - */ -public final class ResourceConfigDisplayFeatureProducer extends - BaseDataProducer<List<DisplayFeature>> { - private static final boolean DEBUG = false; - private static final String TAG = "ResourceConfigDisplayFeatureProducer"; - - private final Context mContext; - - public ResourceConfigDisplayFeatureProducer(@NonNull Context context) { - mContext = context; - } - - @Override - @Nullable - public Optional<List<DisplayFeature>> getData() { - String displayFeaturesString = mContext.getResources().getString( - R.string.config_display_features); - if (TextUtils.isEmpty(displayFeaturesString)) { - return Optional.empty(); - } - - List<DisplayFeature> features = new ArrayList<>(); - String[] featureStrings = displayFeaturesString.split(";"); - for (String featureString : featureStrings) { - CommonDisplayFeature feature; - try { - feature = CommonDisplayFeature.parseFromString(featureString); - } catch (IllegalArgumentException e) { - if (DEBUG) { - Log.w(TAG, "Failed to parse display feature: " + featureString, e); - } - continue; - } - features.add(feature); - } - return Optional.of(features); - } -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java deleted file mode 100644 index 2026df3fa979..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2021 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.common; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; - -import androidx.window.util.BaseDataProducer; - -import java.util.Optional; - -/** - * Implementation of {@link androidx.window.util.DataProducer} that provides the device posture - * as an {@link Integer} from a value stored in {@link Settings}. - */ -public final class SettingsDevicePostureProducer extends BaseDataProducer<Integer> { - private static final String DEVICE_POSTURE = "device_posture"; - - private final Uri mDevicePostureUri = - Settings.Global.getUriFor(DEVICE_POSTURE); - - private final ContentResolver mResolver; - private final ContentObserver mObserver; - private boolean mRegisteredObservers; - - public SettingsDevicePostureProducer(@NonNull Context context) { - mResolver = context.getContentResolver(); - mObserver = new SettingsObserver(); - } - - @Override - @Nullable - public Optional<Integer> getData() { - int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, -1); - return posture == -1 ? Optional.empty() : Optional.of(posture); - } - - /** - * Registers settings observers, if needed. When settings observers are registered for this - * producer callbacks for changes in data will be triggered. - */ - public void registerObserversIfNeeded() { - if (mRegisteredObservers) { - return; - } - mRegisteredObservers = true; - mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */, - mObserver /* ContentObserver */); - } - - /** - * Unregisters settings observers, if needed. When settings observers are unregistered for this - * producer callbacks for changes in data will not be triggered. - */ - public void unregisterObserversIfNeeded() { - if (!mRegisteredObservers) { - return; - } - mRegisteredObservers = false; - mResolver.unregisterContentObserver(mObserver); - } - - private final class SettingsObserver extends ContentObserver { - SettingsObserver() { - super(new Handler(Looper.getMainLooper())); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (mDevicePostureUri.equals(uri)) { - notifyDataChanged(); - } - } - } -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java index 040662657a74..e9d213e06fa9 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java @@ -16,8 +16,10 @@ package androidx.window.common; +import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; +import static androidx.window.common.CommonFoldingFeature.parseListFromString; + import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -26,22 +28,19 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.text.TextUtils; -import android.util.Log; import androidx.window.util.BaseDataProducer; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; /** * Implementation of {@link androidx.window.util.DataProducer} that produces - * {@link CommonDisplayFeature} parsed from a string stored in {@link Settings}. + * {@link CommonFoldingFeature} parsed from a string stored in {@link Settings}. */ public final class SettingsDisplayFeatureProducer - extends BaseDataProducer<List<DisplayFeature>> { - private static final boolean DEBUG = false; - private static final String TAG = "SettingsDisplayFeatureProducer"; + extends BaseDataProducer<List<CommonFoldingFeature>> { private static final String DISPLAY_FEATURES = "display_features"; private final Uri mDisplayFeaturesUri = @@ -57,32 +56,17 @@ public final class SettingsDisplayFeatureProducer } @Override - @Nullable - public Optional<List<DisplayFeature>> getData() { + @NonNull + public Optional<List<CommonFoldingFeature>> getData() { String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); if (displayFeaturesString == null) { return Optional.empty(); } - List<DisplayFeature> features = new ArrayList<>(); if (TextUtils.isEmpty(displayFeaturesString)) { - return Optional.of(features); - } - String[] featureStrings = displayFeaturesString.split(";"); - - for (String featureString : featureStrings) { - CommonDisplayFeature feature; - try { - feature = CommonDisplayFeature.parseFromString(featureString); - } catch (IllegalArgumentException e) { - if (DEBUG) { - Log.w(TAG, "Failed to parse display feature: " + featureString, e); - } - continue; - } - features.add(feature); + return Optional.of(Collections.emptyList()); } - return Optional.of(features); + return Optional.of(parseListFromString(displayFeaturesString, COMMON_STATE_UNKNOWN)); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 8f368c2bee22..fc955927f3ed 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -28,7 +28,6 @@ import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityOptions; import android.app.ActivityThread; -import android.app.Application.ActivityLifecycleCallbacks; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; @@ -41,6 +40,8 @@ import android.os.Looper; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; +import androidx.window.common.EmptyLifecycleCallbacksAdapter; + import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -759,11 +760,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return shouldRetainAssociatedContainer(finishingContainer, associatedContainer); } - private final class LifecycleCallbacks implements ActivityLifecycleCallbacks { - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - } + private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter { @Override public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) { @@ -775,30 +772,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onActivityStarted(Activity activity) { - } - - @Override - public void onActivityResumed(Activity activity) { - } - - @Override - public void onActivityPaused(Activity activity) { - } - - @Override - public void onActivityStopped(Activity activity) { - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - } - - @Override - public void onActivityDestroyed(Activity activity) { - } - - @Override public void onActivityConfigurationChanged(Activity activity) { SplitController.this.onActivityConfigurationChanged(activity); } @@ -833,8 +806,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (shouldExpand(null, intent, getSplitRules())) { setLaunchingInExpandedContainer(launchingActivity, options); - } else if (!setLaunchingToSideContainer(launchingActivity, intent, options)) { - setLaunchingInSameContainer(launchingActivity, intent, options); + } else if (!splitWithLaunchingActivity(launchingActivity, intent, options)) { + setLaunchingInSameSideContainer(launchingActivity, intent, options); } return super.onStartActivity(who, intent, options); @@ -853,9 +826,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Returns {@code true} if the activity that is going to be started via the * {@code intent} should be paired with the {@code launchingActivity} and is set to be - * launched in an empty side container. + * launched in the side container. */ - private boolean setLaunchingToSideContainer(Activity launchingActivity, Intent intent, + private boolean splitWithLaunchingActivity(Activity launchingActivity, Intent intent, Bundle options) { final SplitPairRule splitPairRule = getSplitRule(launchingActivity, intent, getSplitRules()); @@ -863,9 +836,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } - // Create a new split with an empty side container - final TaskFragmentContainer secondaryContainer = mPresenter - .createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule); + // Check if there is any existing side container to launch into. + TaskFragmentContainer secondaryContainer = findSideContainerForNewLaunch( + launchingActivity, splitPairRule); + if (secondaryContainer == null) { + // Create a new split with an empty side container. + secondaryContainer = mPresenter + .createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule); + } // Amend the request to let the WM know that the activity should be placed in the // dedicated container. @@ -875,12 +853,39 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** + * Finds if there is an existing split side {@link TaskFragmentContainer} that can be used + * for the new rule. + */ + @Nullable + private TaskFragmentContainer findSideContainerForNewLaunch(Activity launchingActivity, + SplitPairRule splitPairRule) { + final TaskFragmentContainer launchingContainer = getContainerWithActivity( + launchingActivity.getActivityToken()); + if (launchingContainer == null) { + return null; + } + + // We only check if the launching activity is the primary of the split. We will check + // if the launching activity is the secondary in #setLaunchingInSameSideContainer. + final SplitContainer splitContainer = getActiveSplitForContainer(launchingContainer); + if (splitContainer == null + || splitContainer.getPrimaryContainer() != launchingContainer) { + return null; + } + + if (canReuseContainer(splitPairRule, splitContainer.getSplitRule())) { + return splitContainer.getSecondaryContainer(); + } + return null; + } + + /** * Checks if the activity that is going to be started via the {@code intent} should be * paired with the existing top activity which is currently paired with the - * {@code launchingActivity}. If so, set the activity to be launched in the same + * {@code launchingActivity}. If so, set the activity to be launched in the same side * container of the {@code launchingActivity}. */ - private void setLaunchingInSameContainer(Activity launchingActivity, Intent intent, + private void setLaunchingInSameSideContainer(Activity launchingActivity, Intent intent, Bundle options) { final TaskFragmentContainer launchingContainer = getContainerWithActivity( launchingActivity.getActivityToken()); @@ -911,6 +916,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } + // Can only launch in the same container if the rules share the same presentation. + if (!canReuseContainer(splitPairRule, splitContainer.getSplitRule())) { + return; + } + // Amend the request to let the WM know that the activity should be placed in the // dedicated container. This is necessary for the case that the activity is started // into a new Task, or new Task will be escaped from the current host Task and be @@ -927,4 +937,31 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen public boolean isActivityEmbedded(@NonNull Activity activity) { return mPresenter.isActivityEmbedded(activity.getActivityToken()); } + + /** + * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if + * there is any. + */ + private static boolean canReuseContainer(SplitRule rule1, SplitRule rule2) { + if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) { + return false; + } + return rule1.getSplitRatio() == rule2.getSplitRatio() + && rule1.getLayoutDirection() == rule2.getLayoutDirection(); + } + + /** + * Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given + * rule. + */ + private static boolean isContainerReusableRule(SplitRule rule) { + // We don't expect to reuse the placeholder rule. + if (!(rule instanceof SplitPairRule)) { + return false; + } + final SplitPairRule pairRule = (SplitPairRule) rule; + + // Not reuse if it needs to destroy the existing. + return !pairRule.shouldClearTop(); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java index 89d7a407e459..b3becad3dc5a 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java @@ -33,6 +33,12 @@ import androidx.annotation.NonNull; * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close. */ class TaskFragmentAnimationAdapter { + + /** + * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer. + */ + private static final int LAYER_NO_OVERRIDE = -1; + final Animation mAnimation; final RemoteAnimationTarget mTarget; final SurfaceControl mLeash; @@ -42,6 +48,7 @@ class TaskFragmentAnimationAdapter { final float[] mVecs = new float[4]; final Rect mRect = new Rect(); private boolean mIsFirstFrame = true; + private int mOverrideLayer = LAYER_NO_OVERRIDE; TaskFragmentAnimationAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { @@ -58,10 +65,21 @@ class TaskFragmentAnimationAdapter { mLeash = leash; } + /** + * Surface layer to be set at the first frame of the animation. We will not set the layer if it + * is set to {@link #LAYER_NO_OVERRIDE}. + */ + final void overrideLayer(int layer) { + mOverrideLayer = layer; + } + /** Called on frame update. */ final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) { if (mIsFirstFrame) { t.show(mLeash); + if (mOverrideLayer != LAYER_NO_OVERRIDE) { + t.setLayer(mLeash, mOverrideLayer); + } mIsFirstFrame = false; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 46bdf6d0e689..1ac33173668b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -25,6 +25,7 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; +import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; import android.animation.Animator; import android.animation.ValueAnimator; @@ -181,18 +182,22 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { - return createOpenCloseAnimationAdapters(targets, + return createOpenCloseAnimationAdapters(targets, true /* isOpening */, mAnimationSpec::loadOpenAnimation); } private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { - return createOpenCloseAnimationAdapters(targets, + return createOpenCloseAnimationAdapters(targets, false /* isOpening */, mAnimationSpec::loadCloseAnimation); } + /** + * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition. + * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type. + */ private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters( - @NonNull RemoteAnimationTarget[] targets, + @NonNull RemoteAnimationTarget[] targets, boolean isOpening, @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) { // We need to know if the target window is only a partial of the whole animation screen. // If so, we will need to adjust it to make the whole animation screen looks like one. @@ -210,14 +215,25 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { } } + // For OPEN transition, open windows should be above close windows. + // For CLOSE transition, open windows should be below close windows. + int offsetLayer = TYPE_LAYER_OFFSET; final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>(); for (RemoteAnimationTarget target : openingTargets) { - adapters.add(createOpenCloseAnimationAdapter(target, animationProvider, - openingWholeScreenBounds)); + final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target, + animationProvider, openingWholeScreenBounds); + if (isOpening) { + adapter.overrideLayer(offsetLayer++); + } + adapters.add(adapter); } for (RemoteAnimationTarget target : closingTargets) { - adapters.add(createOpenCloseAnimationAdapter(target, animationProvider, - closingWholeScreenBounds)); + final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target, + animationProvider, closingWholeScreenBounds); + if (!isOpening) { + adapter.overrideLayer(offsetLayer++); + } + adapters.add(adapter); } return adapters; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index fe9ce971d4d9..ee8cb48e3c4c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -18,22 +18,24 @@ package androidx.window.extensions.layout; import static android.view.Display.DEFAULT_DISPLAY; -import static androidx.window.common.DisplayFeature.COMMON_STATE_FLAT; -import static androidx.window.common.DisplayFeature.COMMON_STATE_HALF_OPENED; +import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT; +import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED; import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.annotation.Nullable; import android.app.Activity; +import android.app.Application; import android.content.Context; import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; import android.util.Log; import androidx.annotation.NonNull; -import androidx.window.common.DeviceStateManagerPostureProducer; -import androidx.window.common.DisplayFeature; -import androidx.window.common.ResourceConfigDisplayFeatureProducer; -import androidx.window.common.SettingsDevicePostureProducer; +import androidx.window.common.CommonFoldingFeature; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; +import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.SettingsDisplayFeatureProducer; import androidx.window.util.DataProducer; import androidx.window.util.PriorityDataProducer; @@ -56,36 +58,27 @@ import java.util.function.Consumer; */ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private static final String TAG = "SampleExtension"; - private static WindowLayoutComponent sInstance; private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = new HashMap<>(); - private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; - private final DataProducer<Integer> mDevicePostureProducer; - private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; - private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; + private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; public WindowLayoutComponentImpl(Context context) { - mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); - mDevicePostureProducer = new PriorityDataProducer<>(List.of( - mSettingsDevicePostureProducer, - new DeviceStateManagerPostureProducer(context) - )); - + ((Application) context.getApplicationContext()) + .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); - mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( + mFoldingFeatureProducer = new PriorityDataProducer<>(List.of( mSettingsDisplayFeatureProducer, - new ResourceConfigDisplayFeatureProducer(context) + new DeviceStateManagerFoldingFeatureProducer(context) )); - - mDevicePostureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); - mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); + mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } /** * Adds a listener interested in receiving updates to {@link WindowLayoutInfo} + * * @param activity hosting a {@link android.view.Window} * @param consumer interested in receiving updates to {@link WindowLayoutInfo} */ @@ -97,6 +90,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { /** * Removes a listener no longer interested in receiving updates. + * * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo} */ public void removeWindowLayoutInfoListener( @@ -118,43 +112,34 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return mWindowLayoutChangeListeners.keySet(); } - protected boolean hasListeners() { - return !mWindowLayoutChangeListeners.isEmpty(); + @NonNull + private Boolean isListeningForLayoutChanges(IBinder token) { + for (Activity activity: getActivitiesListeningForLayoutChanges()) { + if (token.equals(activity.getWindow().getAttributes().token)) { + return true; + } + } + return false; } - /** - * Calculate the {@link DisplayFeature.State} from the feature or the device posture producer. - * If the given {@link DisplayFeature.State} is not valid then {@code null} will be returned. - * The {@link FoldingFeature} should be ignored in the case of an invalid - * {@link DisplayFeature.State}. - * - * @param feature a {@link DisplayFeature} to provide the feature state if present. - * @return {@link DisplayFeature.State} of the hinge if present or the state from the posture - * produce if present. - */ - @Nullable - private Integer getFeatureState(DisplayFeature feature) { - Integer featureState = feature.getState(); - Optional<Integer> posture = mDevicePostureProducer.getData(); - Integer state = featureState == null ? posture.orElse(null) : featureState; - return convertToExtensionState(state); + protected boolean hasListeners() { + return !mWindowLayoutChangeListeners.isEmpty(); } /** * A convenience method to translate from the common feature state to the extensions feature - * state. More specifically, translates from {@link DisplayFeature.State} to + * state. More specifically, translates from {@link CommonFoldingFeature.State} to * {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not * possible to translate, then we will return a {@code null} value. * - * @param state if it matches a value in {@link DisplayFeature.State}, {@code null} otherwise. - * @return a {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED} if - * the given state matches a value in {@link DisplayFeature.State} and {@code null} otherwise. + * @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null} + * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or + * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in + * {@link CommonFoldingFeature.State} and {@code null} otherwise. */ @Nullable - private Integer convertToExtensionState(@Nullable Integer state) { - if (state == null) { // The null check avoids a NullPointerException. - return null; - } else if (state == COMMON_STATE_FLAT) { + private Integer convertToExtensionState(int state) { + if (state == COMMON_STATE_FLAT) { return FoldingFeature.STATE_FLAT; } else if (state == COMMON_STATE_HALF_OPENED) { return FoldingFeature.STATE_HALF_OPENED; @@ -172,33 +157,30 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @NonNull private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) { - List<androidx.window.extensions.layout.DisplayFeature> displayFeatures = - getDisplayFeatures(activity); + List<DisplayFeature> displayFeatures = getDisplayFeatures(activity); return new WindowLayoutInfo(displayFeatures); } /** - * Translate from the {@link DisplayFeature} to - * {@link androidx.window.extensions.layout.DisplayFeature} for a given {@link Activity}. If a - * {@link DisplayFeature} is not valid then it will be omitted. + * Translate from the {@link CommonFoldingFeature} to + * {@link DisplayFeature} for a given {@link Activity}. If a + * {@link CommonFoldingFeature} is not valid then it will be omitted. * * For a {@link FoldingFeature} the bounds are localized into the {@link Activity} window - * coordinate space and the state is calculated either from {@link DisplayFeature#getState()} or - * {@link #mDisplayFeatureProducer}. The state from {@link #mDisplayFeatureProducer} may not be - * valid since {@link #mDisplayFeatureProducer} is a general state controller. If the state is - * not valid, the {@link FoldingFeature} is omitted from the {@link List} of - * {@link androidx.window.extensions.layout.DisplayFeature}. If the bounds are not valid, - * constructing a {@link FoldingFeature} will throw an {@link IllegalArgumentException} since - * this can cause negative UI effects down stream. + * coordinate space and the state is calculated from {@link CommonFoldingFeature#getState()}. + * The state from {@link #mFoldingFeatureProducer} may not be valid since + * {@link #mFoldingFeatureProducer} is a general state controller. If the state is not valid, + * the {@link FoldingFeature} is omitted from the {@link List} of {@link DisplayFeature}. If + * the bounds are not valid, constructing a {@link FoldingFeature} will throw an + * {@link IllegalArgumentException} since this can cause negative UI effects down stream. * * @param activity a proxy for the {@link android.view.Window} that contains the - * {@link androidx.window.extensions.layout.DisplayFeature}. - * @return a {@link List} of valid {@link androidx.window.extensions.layout.DisplayFeature} that + * {@link DisplayFeature}. + * @return a {@link List} of valid {@link DisplayFeature} that * are within the {@link android.view.Window} of the {@link Activity} */ - private List<androidx.window.extensions.layout.DisplayFeature> getDisplayFeatures( - @NonNull Activity activity) { - List<androidx.window.extensions.layout.DisplayFeature> features = new ArrayList<>(); + private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) { + List<DisplayFeature> features = new ArrayList<>(); int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); @@ -211,11 +193,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return features; } - Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); + Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); if (storedFeatures.isPresent()) { - - for (DisplayFeature baseFeature : storedFeatures.get()) { - Integer state = getFeatureState(baseFeature); + for (CommonFoldingFeature baseFeature : storedFeatures.get()) { + Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { continue; } @@ -223,8 +204,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { rotateRectToDisplayRotation(displayId, featureRect); transformToWindowSpaceRect(activity, featureRect); - features.add(new FoldingFeature(featureRect, baseFeature.getType(), - getFeatureState(baseFeature))); + features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } } return features; @@ -232,13 +212,31 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private void updateRegistrations() { if (hasListeners()) { - mSettingsDevicePostureProducer.registerObserversIfNeeded(); mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); } else { - mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); } - onDisplayFeaturesChanged(); } + + private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + super.onActivityCreated(activity, savedInstanceState); + onDisplayFeaturesChangedIfListening(activity); + } + + @Override + public void onActivityConfigurationChanged(Activity activity) { + super.onActivityConfigurationChanged(activity); + onDisplayFeaturesChangedIfListening(activity); + } + + private void onDisplayFeaturesChangedIfListening(Activity activity) { + IBinder token = activity.getWindow().getAttributes().token; + if (token == null || isListeningForLayoutChanges(token)) { + onDisplayFeaturesChanged(); + } + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index aa949f126154..c7b709347060 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -23,16 +23,17 @@ import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.app.Activity; import android.app.ActivityThread; +import android.app.Application; import android.content.Context; import android.graphics.Rect; +import android.os.Bundle; import android.os.IBinder; import android.util.Log; import androidx.annotation.NonNull; -import androidx.window.common.DeviceStateManagerPostureProducer; -import androidx.window.common.DisplayFeature; -import androidx.window.common.ResourceConfigDisplayFeatureProducer; -import androidx.window.common.SettingsDevicePostureProducer; +import androidx.window.common.CommonFoldingFeature; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; +import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.SettingsDisplayFeatureProducer; import androidx.window.util.DataProducer; import androidx.window.util.PriorityDataProducer; @@ -48,36 +49,25 @@ import java.util.Optional; */ class SampleSidecarImpl extends StubSidecar { private static final String TAG = "SampleSidecar"; - private static final boolean DEBUG = false; - private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; - private final DataProducer<Integer> mDevicePostureProducer; + private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; - private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; - private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; + private final SettingsDisplayFeatureProducer mSettingsFoldingFeatureProducer; SampleSidecarImpl(Context context) { - mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); - mDevicePostureProducer = new PriorityDataProducer<>(List.of( - mSettingsDevicePostureProducer, - new DeviceStateManagerPostureProducer(context) + ((Application) context.getApplicationContext()) + .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); + mSettingsFoldingFeatureProducer = new SettingsDisplayFeatureProducer(context); + mFoldingFeatureProducer = new PriorityDataProducer<>(List.of( + mSettingsFoldingFeatureProducer, + new DeviceStateManagerFoldingFeatureProducer(context) )); - mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); - mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( - mSettingsDisplayFeatureProducer, - new ResourceConfigDisplayFeatureProducer(context) - )); - - mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged); - mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); - } - - private void onDevicePostureChanged() { - updateDeviceState(getDeviceState()); + mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } private void onDisplayFeaturesChanged() { + updateDeviceState(getDeviceState()); for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); updateWindowLayout(windowToken, newLayout); @@ -87,27 +77,21 @@ class SampleSidecarImpl extends StubSidecar { @NonNull @Override public SidecarDeviceState getDeviceState() { - Optional<Integer> posture = mDevicePostureProducer.getData(); - SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = posture.orElse(deviceStateFromFeature()); + deviceState.posture = deviceStateFromFeature(); return deviceState; } private int deviceStateFromFeature() { - List<DisplayFeature> storedFeatures = mDisplayFeatureProducer.getData() + List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData() .orElse(Collections.emptyList()); for (int i = 0; i < storedFeatures.size(); i++) { - DisplayFeature feature = storedFeatures.get(i); - final int state = feature.getState() == null ? -1 : feature.getState(); - if (DEBUG && feature.getState() == null) { - Log.d(TAG, "feature#getState was null for DisplayFeature: " + feature); - } - + CommonFoldingFeature feature = storedFeatures.get(i); + final int state = feature.getState(); switch (state) { - case DisplayFeature.COMMON_STATE_FLAT: + case CommonFoldingFeature.COMMON_STATE_FLAT: return SidecarDeviceState.POSTURE_OPENED; - case DisplayFeature.COMMON_STATE_HALF_OPENED: + case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: return SidecarDeviceState.POSTURE_HALF_OPENED; } } @@ -127,22 +111,22 @@ class SampleSidecarImpl extends StubSidecar { } private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { - List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>(); int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); - return features; + return Collections.emptyList(); } if (activity.isInMultiWindowMode()) { // It is recommended not to report any display features in multi-window mode, since it // won't be possible to synchronize the display feature positions with window movement. - return features; + return Collections.emptyList(); } - Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); + Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); + List<SidecarDisplayFeature> features = new ArrayList<>(); if (storedFeatures.isPresent()) { - for (DisplayFeature baseFeature : storedFeatures.get()) { + for (CommonFoldingFeature baseFeature : storedFeatures.get()) { SidecarDisplayFeature feature = new SidecarDisplayFeature(); Rect featureRect = baseFeature.getRect(); rotateRectToDisplayRotation(displayId, featureRect); @@ -152,17 +136,37 @@ class SampleSidecarImpl extends StubSidecar { features.add(feature); } } - return features; + return Collections.unmodifiableList(features); } @Override protected void onListenersChanged() { if (hasListeners()) { - mSettingsDevicePostureProducer.registerObserversIfNeeded(); - mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); + mSettingsFoldingFeatureProducer.registerObserversIfNeeded(); + onDisplayFeaturesChanged(); } else { - mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); - mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); + mSettingsFoldingFeatureProducer.unregisterObserversIfNeeded(); + } + } + + private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + super.onActivityCreated(activity, savedInstanceState); + onDisplayFeaturesChangedForActivity(activity); + } + + @Override + public void onActivityConfigurationChanged(Activity activity) { + super.onActivityConfigurationChanged(activity); + onDisplayFeaturesChangedForActivity(activity); + } + + private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) { + IBinder token = activity.getWindow().getAttributes().token; + if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) { + onDisplayFeaturesChanged(); + } } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java index 199c37315c07..b9c808a6569b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java @@ -30,7 +30,7 @@ import java.util.Set; abstract class StubSidecar implements SidecarInterface { private SidecarCallback mSidecarCallback; - private final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>(); + final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>(); private boolean mDeviceStateChangeListenerRegistered; StubSidecar() { diff --git a/packages/SystemUI/docs/usb_audio.md b/packages/SystemUI/docs/usb_audio.md new file mode 100644 index 000000000000..66e2df944fbc --- /dev/null +++ b/packages/SystemUI/docs/usb_audio.md @@ -0,0 +1,30 @@ +# USB audio Permission and Confirmation warning dialog resource string id matrix table +### go/support-usb-access-aoc-offload-feature + + |---|------------|----------------|------------------|-----------------|--------------------| + | # | Permission |isUsbAudioDevice| hasAudioPlayback | hasAudioCapture | string resource ID | + |---|------------|----------------|------------------|-----------------|--------------------| + | 1 | TRUE | TRUE | TRUE | FALSE | usb_audio_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 2 | TRUE | TRUE | FALSE | TRUE | usb_audio_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 3 | TRUE | TRUE | TRUE | TRUE | usb_audio_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 4 | TRUE | FALSE | N/A | N/A | usb_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 5 | FALSE | TRUE | TRUE | FALSE | usb_audio_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 6 | FALSE | TRUE | FALSE | TRUE | usb_audio_device_ + permission_prompt_warn + |---|------------|----------------|------------------|-----------------|--------------------| + | 7 | FALSE | TRUE | TRUE | TRUE | usb_audio_device_ + permission_prompt_warn + |---|------------|----------------|------------------|-----------------|--------------------| + | 8 | FALSE | FALSE | N/A | N/A | usb_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e1afd3fe6a30..4a7d7089d712 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -78,6 +78,18 @@ <!-- Checkbox label for USB device dialogs with warning text for USB device dialogs. [CHAR LIMIT=200]--> <string name="usb_device_permission_prompt_warn">Allow <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> to access <xliff:g id="usb_device" example="USB Headphones">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device.</string> + <!-- USB audio device permission dialog title. [CHAR LIMIT=200]--> + <string name="usb_audio_device_permission_prompt_title">Allow <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> to access <xliff:g id="usb_device" example="USB Headphones">%2$s</xliff:g>?</string> + + <!-- USB audio device confirm dialog title. [CHAR LIMIT=200]--> + <string name="usb_audio_device_confirm_prompt_title">Open <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> to handle <xliff:g id="usb_device" example="USB Headphones">%2$s</xliff:g>?</string> + + <!-- Checkbox label for USB audio device dialogs with warning text for USB audio device dialogs. [CHAR LIMIT=NONE]--> + <string name="usb_audio_device_prompt_warn">This app has not been granted record permission but could capture audio through this USB device. Using <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> with this device might prevent hearing calls, notifications and alarms.</string> + + <!-- Prompt for the USB audio device permission dialog [CHAR LIMIT=NONE] --> + <string name="usb_audio_device_prompt">Using <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> with this device might prevent hearing calls, notifications and alarms.</string> + <!-- Prompt for the USB accessory permission dialog [CHAR LIMIT=80] --> <string name="usb_accessory_permission_prompt">Allow <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> to access <xliff:g id="usb_accessory" example="USB Dock">%2$s</xliff:g>?</string> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e9f288d51317..8d9fbca418f6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -112,6 +112,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.DozeParameters; @@ -324,6 +325,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, // the properties of the keyguard private final KeyguardUpdateMonitor mUpdateMonitor; + private final Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy; /** * Last SIM state reported by the telephony system. @@ -846,7 +848,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, - Lazy<NotificationShadeDepthController> notificationShadeDepthController) { + Lazy<NotificationShadeDepthController> notificationShadeDepthController, + Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy) { super(context); mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; @@ -862,6 +865,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mKeyguardDisplayManager = keyguardDisplayManager; dumpManager.registerDumpable(getClass().getName(), this); mDeviceConfig = deviceConfig; + mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy; mShowHomeOverLockscreen = mDeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, @@ -1484,7 +1488,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, public void doKeyguardTimeout(Bundle options) { mHandler.removeMessages(KEYGUARD_TIMEOUT); Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT, options); - mHandler.sendMessage(msg); + // Treat these messages with priority - A call to timeout means the device should lock + // as soon as possible and not wait for other messages on the thread to process first. + mHandler.sendMessageAtFrontOfQueue(msg); } /** @@ -1673,12 +1679,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, * @see #handleShow */ private void showLocked(Bundle options) { - Trace.beginSection("KeyguardViewMediator#showLocked aqcuiring mShowKeyguardWakeLock"); + Trace.beginSection("KeyguardViewMediator#showLocked acquiring mShowKeyguardWakeLock"); if (DEBUG) Log.d(TAG, "showLocked"); // ensure we stay awake until we are finished displaying the keyguard mShowKeyguardWakeLock.acquire(); Message msg = mHandler.obtainMessage(SHOW, options); - mHandler.sendMessage(msg); + // Treat these messages with priority - This call can originate from #doKeyguardTimeout, + // meaning the device should lock as soon as possible and not wait for other messages on + // the thread to process first. + mHandler.sendMessageAtFrontOfQueue(msg); Trace.endSection(); } @@ -1879,6 +1888,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, case KEYGUARD_TIMEOUT: synchronized (KeyguardViewMediator.this) { doKeyguardLocked((Bundle) msg.obj); + notifyDefaultDisplayCallbacks(mShowing); } break; case DISMISS: @@ -1889,10 +1899,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, Trace.beginSection( "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM"); StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj; - handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration, - params.mApps, params.mWallpapers, params.mNonApps, - params.mFinishedCallback); - mFalsingCollector.onSuccessfulUnlock(); + mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams( + () -> { + handleStartKeyguardExitAnimation(params.startTime, + params.fadeoutDuration, params.mApps, params.mWallpapers, + params.mNonApps, params.mFinishedCallback); + mFalsingCollector.onSuccessfulUnlock(); + }); Trace.endSection(); break; case CANCEL_KEYGUARD_EXIT_ANIM: @@ -2190,10 +2203,12 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mKeyguardGoingAwayRunnable.run(); } else { // TODO(bc-unlock): Fill parameters - handleStartKeyguardExitAnimation( - SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(), - mHideAnimation.getDuration(), null /* apps */, null /* wallpapers */, - null /* nonApps */, null /* finishedCallback */); + mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(() -> { + handleStartKeyguardExitAnimation( + SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(), + mHideAnimation.getDuration(), null /* apps */, null /* wallpapers */, + null /* nonApps */, null /* finishedCallback */); + }); } } Trace.endSection(); @@ -2888,7 +2903,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, for (int i = size - 1; i >= 0; i--) { IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i); try { - callback.onShowingStateChanged(showing); + callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser()); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onShowingStateChanged", e); if (e instanceof DeadObjectException) { @@ -2922,7 +2937,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mKeyguardStateCallbacks.add(callback); try { callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure()); - callback.onShowingStateChanged(mShowing); + callback.onShowingStateChanged(mShowing, KeyguardUpdateMonitor.getCurrentUser()); callback.onInputRestrictedStateChanged(mInputRestricted); callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust( KeyguardUpdateMonitor.getCurrentUser())); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index cae9feeb62eb..88dcf6d35075 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -42,6 +42,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardLiftController; @@ -97,7 +98,8 @@ public class KeyguardModule { KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, - Lazy<NotificationShadeDepthController> notificationShadeDepthController) { + Lazy<NotificationShadeDepthController> notificationShadeDepthController, + Lazy<NotificationShadeWindowController> notificationShadeWindowController) { return new KeyguardViewMediator( context, falsingCollector, @@ -120,7 +122,8 @@ public class KeyguardModule { keyguardStateController, keyguardUnlockAnimationController, unlockedScreenOffAnimationController, - notificationShadeDepthController + notificationShadeDepthController, + notificationShadeWindowController ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 6ea79af8b9ad..65ff5583e7d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import android.view.ViewGroup; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; @@ -188,6 +189,14 @@ public interface NotificationShadeWindowController extends RemoteInputController default void setLightRevealScrimOpaque(boolean opaque) {} /** + * Defer any application of window {@link WindowManager.LayoutParams} until {@code scope} is + * fully applied. + */ + default void batchApplyWindowLayoutParams(@NonNull Runnable scope) { + scope.run(); + } + + /** * Custom listener to pipe data back to plugins about whether or not the status bar would be * collapsed if not for the plugin. * TODO: Find cleaner way to do this. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 33c4109b3426..3ba66bff66bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -145,6 +145,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.RemoteInputController; @@ -664,7 +665,9 @@ public class NotificationPanelViewController extends PanelViewController { NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationEntryManager notificationEntryManager, KeyguardStateController keyguardStateController, - StatusBarStateController statusBarStateController, DozeLog dozeLog, + StatusBarStateController statusBarStateController, + NotificationShadeWindowController notificationShadeWindowController, + DozeLog dozeLog, DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper, LatencyTracker latencyTracker, PowerManager powerManager, AccessibilityManager accessibilityManager, @DisplayId int displayId, @@ -716,6 +719,7 @@ public class NotificationPanelViewController extends PanelViewController { dozeLog, keyguardStateController, (SysuiStatusBarStateController) statusBarStateController, + notificationShadeWindowController, vibratorHelper, statusBarKeyguardViewManager, latencyTracker, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 030a8951943d..8c76a1bf4f83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -107,6 +107,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final SysuiColorExtractor mColorExtractor; private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE; + /** + * Layout params would be aggregated and dispatched all at once if this is > 0. + * + * @see #batchApplyWindowLayoutParams(Runnable) + */ + private int mDeferWindowLayoutParams; @Inject public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager, @@ -433,6 +439,20 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } + private void applyWindowLayoutParams() { + if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) { + mWindowManager.updateViewLayout(mNotificationShadeView, mLp); + } + } + + @Override + public void batchApplyWindowLayoutParams(Runnable scope) { + mDeferWindowLayoutParams++; + scope.run(); + mDeferWindowLayoutParams--; + applyWindowLayoutParams(); + } + private void apply(State state) { applyKeyguardFlags(state); applyFocusableFlag(state); @@ -447,9 +467,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW applyHasTopUi(state); applyNotTouchable(state); applyStatusBarColorSpaceAgnosticFlag(state); - if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { - mWindowManager.updateViewLayout(mNotificationShadeView, mLp); - } + applyWindowLayoutParams(); + if (mHasTopUi != mHasTopUiChanged) { whitelistIpcs(() -> { try { @@ -722,6 +741,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW pw.println(TAG + ":"); pw.println(" mKeyguardMaxRefreshRate=" + mKeyguardMaxRefreshRate); pw.println(" mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate); + pw.println(" mDeferWindowLayoutParams=" + mDeferWindowLayoutParams); pw.println(mCurrentState); if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) { mNotificationShadeView.getViewRootImpl().dump(" ", pw); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 2bf16fc9e52c..040820e90790 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -54,6 +54,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.classifier.Classifier; import com.android.systemui.doze.DozeLog; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; @@ -177,6 +178,7 @@ public abstract class PanelViewController { private boolean mExpandLatencyTracking; private final PanelView mView; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final NotificationShadeWindowController mNotificationShadeWindowController; protected final Resources mResources; protected final KeyguardStateController mKeyguardStateController; protected final SysuiStatusBarStateController mStatusBarStateController; @@ -215,6 +217,7 @@ public abstract class PanelViewController { DozeLog dozeLog, KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, + NotificationShadeWindowController notificationShadeWindowController, VibratorHelper vibratorHelper, StatusBarKeyguardViewManager statusBarKeyguardViewManager, LatencyTracker latencyTracker, @@ -247,6 +250,7 @@ public abstract class PanelViewController { mResources = mView.getResources(); mKeyguardStateController = keyguardStateController; mStatusBarStateController = statusBarStateController; + mNotificationShadeWindowController = notificationShadeWindowController; mFlingAnimationUtils = flingAnimationUtilsBuilder .reset() .setMaxLengthSeconds(0.6f) @@ -743,34 +747,36 @@ public abstract class PanelViewController { if (isNaN(h)) { Log.wtf(TAG, "ExpandedHeight set to NaN"); } - if (mExpandLatencyTracking && h != 0f) { - DejankUtils.postAfterTraversal( - () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); - mExpandLatencyTracking = false; - } - float maxPanelHeight = getMaxPanelHeight(); - if (mHeightAnimator == null) { - if (mTracking) { - float overExpansionPixels = Math.max(0, h - maxPanelHeight); - setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); + mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { + if (mExpandLatencyTracking && h != 0f) { + DejankUtils.postAfterTraversal( + () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); + mExpandLatencyTracking = false; + } + float maxPanelHeight = getMaxPanelHeight(); + if (mHeightAnimator == null) { + if (mTracking) { + float overExpansionPixels = Math.max(0, h - maxPanelHeight); + setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); + } + mExpandedHeight = Math.min(h, maxPanelHeight); + } else { + mExpandedHeight = h; } - mExpandedHeight = Math.min(h, maxPanelHeight); - } else { - mExpandedHeight = h; - } - // If we are closing the panel and we are almost there due to a slow decelerating - // interpolator, abort the animation. - if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { - mExpandedHeight = 0f; - if (mHeightAnimator != null) { - mHeightAnimator.end(); + // If we are closing the panel and we are almost there due to a slow decelerating + // interpolator, abort the animation. + if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { + mExpandedHeight = 0f; + if (mHeightAnimator != null) { + mHeightAnimator.end(); + } } - } - mExpandedFraction = Math.min(1f, - maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); - onHeightUpdated(mExpandedHeight); - updatePanelExpansionAndVisibility(); + mExpandedFraction = Math.min(1f, + maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); + onHeightUpdated(mExpandedHeight); + updatePanelExpansionAndVisibility(); + }); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 071c8c9117ec..0eefc9404825 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -2937,9 +2937,8 @@ public class StatusBar extends SystemUI implements // turned off fully. boolean keyguardForDozing = mDozeServiceHost.getDozingRequested() && (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard)); - boolean isWakingAndOccluded = isOccluded() && isWaking(); boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested() - || keyguardForDozing) && !wakeAndUnlocking && !isWakingAndOccluded; + || keyguardForDozing) && !wakeAndUnlocking; if (keyguardForDozing) { updatePanelExpansionForKeyguard(); } @@ -3560,26 +3559,29 @@ public class StatusBar extends SystemUI implements public void onStartedWakingUp() { String tag = "StatusBar#onStartedWakingUp"; DejankUtils.startDetectingBlockingIpcs(tag); - mDeviceInteractive = true; - mWakeUpCoordinator.setWakingUp(true); - if (!mKeyguardBypassController.getBypassEnabled()) { - mHeadsUpManager.releaseAllImmediately(); - } - updateVisibleToUser(); - updateIsKeyguard(); - mDozeServiceHost.stopDozing(); - // This is intentionally below the stopDozing call above, since it avoids that we're - // unnecessarily animating the wakeUp transition. Animations should only be enabled - // once we fully woke up. - updateRevealEffect(true /* wakingUp */); - updateNotificationPanelTouchState(); - - // If we are waking up during the screen off animation, we should undo making the - // expanded visible (we did that so the LightRevealScrim would be visible). - if (mUnlockedScreenOffAnimationController.isScreenOffLightRevealAnimationPlaying()) { - makeExpandedInvisible(); - } + mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { + mDeviceInteractive = true; + mWakeUpCoordinator.setWakingUp(true); + if (!mKeyguardBypassController.getBypassEnabled()) { + mHeadsUpManager.releaseAllImmediately(); + } + updateVisibleToUser(); + updateIsKeyguard(); + mDozeServiceHost.stopDozing(); + // This is intentionally below the stopDozing call above, since it avoids that we're + // unnecessarily animating the wakeUp transition. Animations should only be enabled + // once we fully woke up. + updateRevealEffect(true /* wakingUp */); + updateNotificationPanelTouchState(); + + // If we are waking up during the screen off animation, we should undo making the + // expanded visible (we did that so the LightRevealScrim would be visible). + if (mUnlockedScreenOffAnimationController + .isScreenOffLightRevealAnimationPlaying()) { + makeExpandedInvisible(); + } + }); DejankUtils.stopDetectingBlockingIpcs(tag); } @@ -3717,10 +3719,6 @@ public class StatusBar extends SystemUI implements == WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP; } - boolean isWaking() { - return mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_WAKING; - } - public void notifyBiometricAuthModeChanged() { mDozeServiceHost.updateDozing(); updateScrimController(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 12258136c011..67985b95dda4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -21,7 +21,7 @@ import android.app.IActivityManager import android.app.IUidObserver import android.app.Notification import android.app.Notification.CallStyle.CALL_TYPE_ONGOING -import android.content.Intent +import android.app.PendingIntent import android.util.Log import android.view.View import androidx.annotation.VisibleForTesting @@ -98,7 +98,7 @@ class OngoingCallController @Inject constructor( val newOngoingCallInfo = CallNotificationInfo( entry.sbn.key, entry.sbn.notification.`when`, - entry.sbn.notification.contentIntent?.intent, + entry.sbn.notification.contentIntent, entry.sbn.uid, entry.sbn.notification.extras.getInt( Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING, @@ -230,7 +230,6 @@ class OngoingCallController @Inject constructor( logger.logChipClicked() activityStarter.postStartActivityDismissingKeyguard( intent, - 0, ActivityLaunchAnimator.Controller.fromView( backgroundView, InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP) @@ -351,7 +350,7 @@ class OngoingCallController @Inject constructor( private data class CallNotificationInfo( val key: String, val callStartTime: Long, - val intent: Intent?, + val intent: PendingIntent?, val uid: Int, /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */ val isOngoing: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbAudioWarningDialogMessage.java b/packages/SystemUI/src/com/android/systemui/usb/UsbAudioWarningDialogMessage.java new file mode 100644 index 000000000000..df845e53a36b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbAudioWarningDialogMessage.java @@ -0,0 +1,119 @@ +/* + * 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.usb; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.util.Log; + +import com.android.systemui.R; + +import java.lang.annotation.Retention; + +/** + * USB Audio devices warning dialog messages help class. + */ +public class UsbAudioWarningDialogMessage { + private static final String TAG = "UsbAudioWarningDialogMessage"; + + @Retention(SOURCE) + @IntDef({TYPE_PERMISSION, TYPE_CONFIRM}) + public @interface DialogType {} + public static final int TYPE_PERMISSION = 0; + public static final int TYPE_CONFIRM = 1; + + private final int mDialogType; + private UsbDialogHelper mDialogHelper; + + public UsbAudioWarningDialogMessage(Context context, Intent intent, @DialogType int type) { + mDialogType = type; + try { + mDialogHelper = new UsbDialogHelper(context, intent); + } catch (IllegalStateException e) { + Log.e(TAG, "Unable to initialize UsbDialogHelper!", e); + } + } + + private boolean hasRecordPermission() { + return mDialogHelper.packageHasAudioRecordingPermission(); + } + + private boolean isUsbAudioDevice() { + return mDialogHelper.isUsbDevice() && (mDialogHelper.deviceHasAudioCapture() + || (mDialogHelper.deviceHasAudioPlayback())); + } + + private boolean hasAudioPlayback() { + return mDialogHelper.deviceHasAudioPlayback(); + } + + private boolean hasAudioCapture() { + return mDialogHelper.deviceHasAudioCapture(); + } + + /** + * According to USB audio warning dialog matrix table to return warning message id. + * @return string resId for USB audio warning dialog message, otherwise {ID_NULL}. + * See usb_audio.md for USB audio Permission and Confirmation warning dialog resource + * string id matrix table. + */ + public int getMessageId() { + if (!mDialogHelper.isUsbDevice()) { + return getUsbAccessoryPromptId(); + } + + if (hasRecordPermission() && isUsbAudioDevice()) { + // case# 1, 2, 3 + return R.string.usb_audio_device_prompt; + } else if (!hasRecordPermission() && isUsbAudioDevice() && hasAudioPlayback() + && !hasAudioCapture()) { + // case# 5 + return R.string.usb_audio_device_prompt; + } + + if (!hasRecordPermission() && isUsbAudioDevice() && hasAudioCapture()) { + // case# 6,7 + return R.string.usb_audio_device_prompt_warn; + } + + Log.w(TAG, "Only shows title with empty content description!"); + return Resources.ID_NULL; + } + + /** + * Gets prompt dialog title. + * @return string id for USB prompt dialog title. + */ + public int getPromptTitleId() { + return (mDialogType == TYPE_PERMISSION) + ? R.string.usb_audio_device_permission_prompt_title + : R.string.usb_audio_device_confirm_prompt_title; + } + + /** + * Gets USB Accessory prompt message id. + * @return string id for USB Accessory prompt message. + */ + public int getUsbAccessoryPromptId() { + return (mDialogType == TYPE_PERMISSION) + ? R.string.usb_accessory_permission_prompt : R.string.usb_accessory_confirm_prompt; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java index 21d700e41a40..a2bee05246ce 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.PermissionChecker; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; @@ -35,7 +36,6 @@ import android.os.UserHandle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import android.view.Window; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; @@ -57,6 +57,7 @@ public class UsbConfirmActivity extends AlertActivity private ResolveInfo mResolveInfo; private boolean mPermissionGranted; private UsbDisconnectedReceiver mDisconnectedReceiver; + private UsbAudioWarningDialogMessage mUsbConfirmMessageHandler; @Override public void onCreate(Bundle icicle) { @@ -70,7 +71,9 @@ public class UsbConfirmActivity extends AlertActivity mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); mResolveInfo = (ResolveInfo) intent.getParcelableExtra("rinfo"); String packageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE); - + mUsbConfirmMessageHandler = new UsbAudioWarningDialogMessage( + getApplicationContext(), getIntent(), + UsbAudioWarningDialogMessage.TYPE_CONFIRM); PackageManager packageManager = getPackageManager(); String appName = mResolveInfo.loadLabel(packageManager).toString(); @@ -78,8 +81,8 @@ public class UsbConfirmActivity extends AlertActivity ap.mTitle = appName; boolean useRecordWarning = false; if (mDevice == null) { - ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName, - mAccessory.getDescription()); + final int messageId = mUsbConfirmMessageHandler.getUsbAccessoryPromptId(); + ap.mMessage = getString(messageId, appName, mAccessory.getDescription()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); } else { int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); @@ -91,11 +94,11 @@ public class UsbConfirmActivity extends AlertActivity boolean isAudioCaptureDevice = mDevice.getHasAudioCapture(); useRecordWarning = isAudioCaptureDevice && !hasRecordPermission; - int strID = useRecordWarning - ? R.string.usb_device_confirm_prompt_warn - : R.string.usb_device_confirm_prompt; - - ap.mMessage = getString(strID, appName, mDevice.getProductName()); + final int messageId = mUsbConfirmMessageHandler.getMessageId(); + final int titleId = mUsbConfirmMessageHandler.getPromptTitleId(); + ap.mTitle = getString(titleId, appName, mDevice.getProductName()); + ap.mMessage = (messageId != Resources.ID_NULL) ? getString(messageId, appName, + mDevice.getProductName()) : null; mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice); } ap.mPositiveButtonText = getString(android.R.string.ok); diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDialogHelper.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDialogHelper.java new file mode 100644 index 000000000000..ab29a9e970c7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDialogHelper.java @@ -0,0 +1,303 @@ +/* + * 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.usb; + +import static android.Manifest.permission.RECORD_AUDIO; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.PermissionChecker; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.hardware.usb.IUsbManager; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.Log; + +/** + * Helper class to separate model and view for USB permission and confirm dialogs. + */ +public class UsbDialogHelper { + private static final String TAG = UsbDialogHelper.class.getSimpleName(); + private static final String EXTRA_RESOLVE_INFO = "rinfo"; + + private final UsbDevice mDevice; + private final UsbAccessory mAccessory; + private final ResolveInfo mResolveInfo; + private final String mPackageName; + private final CharSequence mAppName; + private final Context mContext; + private final PendingIntent mPendingIntent; + private final IUsbManager mUsbService; + private final int mUid; + private final boolean mCanBeDefault; + + private UsbDisconnectedReceiver mDisconnectedReceiver; + private boolean mIsUsbDevice; + private boolean mResponseSent; + + /** + * @param context The Context of the caller. + * @param intent The intent of the caller. + * @throws IllegalStateException Thrown if both UsbDevice and UsbAccessory are null or if the + * query for the matching ApplicationInfo is unsuccessful. + */ + public UsbDialogHelper(Context context, Intent intent) throws IllegalStateException { + mContext = context; + mDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + mAccessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + mCanBeDefault = intent.getBooleanExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, false); + if (mDevice == null && mAccessory == null) { + throw new IllegalStateException("Device and accessory are both null."); + } + if (mDevice != null) { + mIsUsbDevice = true; + } + mResolveInfo = intent.getParcelableExtra(EXTRA_RESOLVE_INFO); + PackageManager packageManager = mContext.getPackageManager(); + if (mResolveInfo != null) { + // If a ResolveInfo is provided it will be used to determine the activity to start + mUid = mResolveInfo.activityInfo.applicationInfo.uid; + mPackageName = mResolveInfo.activityInfo.packageName; + mPendingIntent = null; + } else { + mUid = intent.getIntExtra(Intent.EXTRA_UID, -1); + mPackageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE); + mPendingIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT); + } + try { + ApplicationInfo aInfo = packageManager.getApplicationInfo(mPackageName, 0); + mAppName = aInfo.loadLabel(packageManager); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("unable to look up package name", e); + } + IBinder b = ServiceManager.getService(Context.USB_SERVICE); + mUsbService = IUsbManager.Stub.asInterface(b); + } + + /** + * Registers UsbDisconnectedReceiver to dismiss dialog automatically when device or accessory + * gets disconnected + * @param activity The activity to finish when device / accessory gets disconnected. + */ + public void registerUsbDisconnectedReceiver(Activity activity) { + if (mIsUsbDevice) { + mDisconnectedReceiver = new UsbDisconnectedReceiver(activity, mDevice); + } else { + mDisconnectedReceiver = new UsbDisconnectedReceiver(activity, mAccessory); + } + } + + /** + * Unregisters the UsbDisconnectedReceiver. To be called when the activity is destroyed. + * @param activity The activity registered to finish when device / accessory gets disconnected. + */ + public void unregisterUsbDisconnectedReceiver(Activity activity) { + if (mDisconnectedReceiver != null) { + try { + activity.unregisterReceiver(mDisconnectedReceiver); + } catch (Exception e) { + // pass + } + mDisconnectedReceiver = null; + } + } + + /** + * @return True if the intent contains a UsbDevice which can capture audio. + */ + public boolean deviceHasAudioCapture() { + return mDevice != null && mDevice.getHasAudioCapture(); + } + + /** + * @return True if the intent contains a UsbDevice which can play audio. + */ + public boolean deviceHasAudioPlayback() { + return mDevice != null && mDevice.getHasAudioPlayback(); + } + + /** + * @return True if the package has RECORD_AUDIO permission specified in its manifest. + */ + public boolean packageHasAudioRecordingPermission() { + return PermissionChecker.checkPermissionForPreflight(mContext, RECORD_AUDIO, + PermissionChecker.PID_UNKNOWN, mUid, mPackageName) + == android.content.pm.PackageManager.PERMISSION_GRANTED; + } + + /** + * @return True if the intent contains a UsbDevice. + */ + public boolean isUsbDevice() { + return mIsUsbDevice; + } + + /** + * @return True if the intent contains a UsbAccessory. + */ + public boolean isUsbAccessory() { + return !mIsUsbDevice; + } + + /** + * Grants USB permission to the device / accessory to the calling uid. + */ + public void grantUidAccessPermission() { + try { + if (mIsUsbDevice) { + mUsbService.grantDevicePermission(mDevice, mUid); + } else { + mUsbService.grantAccessoryPermission(mAccessory, mUid); + } + } catch (RemoteException e) { + Log.e(TAG, "IUsbService connection failed", e); + } + } + + /** + * Sets the package as default for the device / accessory. + */ + public void setDefaultPackage() { + final int userId = UserHandle.myUserId(); + try { + if (mIsUsbDevice) { + mUsbService.setDevicePackage(mDevice, mPackageName, userId); + } else { + mUsbService.setAccessoryPackage(mAccessory, mPackageName, userId); + } + } catch (RemoteException e) { + Log.e(TAG, "IUsbService connection failed", e); + } + } + + /** + * Clears the default package of the device / accessory. + */ + public void clearDefaultPackage() { + final int userId = UserHandle.myUserId(); + try { + if (mIsUsbDevice) { + mUsbService.setDevicePackage(mDevice, null, userId); + } else { + mUsbService.setAccessoryPackage(mAccessory, null, userId); + } + } catch (RemoteException e) { + Log.e(TAG, "IUsbService connection failed", e); + } + } + + /** + * Starts the activity which was selected to handle the device / accessory. + */ + public void confirmDialogStartActivity() { + final int userId = UserHandle.myUserId(); + Intent intent; + + if (mIsUsbDevice) { + intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); + intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); + } else { + intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); + intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); + } + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setComponent( + new ComponentName(mResolveInfo.activityInfo.packageName, + mResolveInfo.activityInfo.name)); + try { + mContext.startActivityAsUser(intent, new UserHandle(userId)); + } catch (Exception e) { + Log.e(TAG, "Unable to start activity", e); + } + } + + /** + * Sends the result of the permission dialog via the provided PendingIntent. + * + * @param permissionGranted True if the user pressed ok in the permission dialog. + */ + public void sendPermissionDialogResponse(boolean permissionGranted) { + if (!mResponseSent) { + // send response via pending intent + Intent intent = new Intent(); + if (mIsUsbDevice) { + intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); + } else { + intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); + } + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, permissionGranted); + try { + mPendingIntent.send(mContext, 0, intent); + mResponseSent = true; + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "PendingIntent was cancelled"); + } + } + } + + /** + * @return A description of the device / accessory + */ + public String getDeviceDescription() { + String desc; + if (mIsUsbDevice) { + desc = mDevice.getProductName(); + if (desc == null) { + desc = mDevice.getDeviceName(); + } + } else { + // UsbAccessory + desc = mAccessory.getDescription(); + if (desc == null) { + desc = String.format("%s %s", mAccessory.getManufacturer(), mAccessory.getModel()); + } + } + return desc; + } + + /** + * Whether the calling package can set as default handler of the USB device or accessory. + * In case of a UsbAccessory this is the case if the calling package has an intent filter for + * {@link UsbManager#ACTION_USB_ACCESSORY_ATTACHED} with a usb-accessory filter matching the + * attached accessory. In case of a UsbDevice this is the case if the calling package has an + * intent filter for {@link UsbManager#ACTION_USB_DEVICE_ATTACHED} with a usb-device filter + * matching the attached device. + * + * @return True if the package can be default for the USB device. + */ + public boolean canBeDefault() { + return mCanBeDefault; + } + + /** + * @return The name of the app which requested permission or the name of the app which will be + * opened if the user allows it to handle the USB device. + */ + public CharSequence getAppName() { + return mAppName; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index bfa50bcee270..b9a37b1918f2 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.PermissionChecker; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; @@ -59,6 +60,7 @@ public class UsbPermissionActivity extends AlertActivity private int mUid; private boolean mPermissionGranted; private UsbDisconnectedReceiver mDisconnectedReceiver; + private UsbAudioWarningDialogMessage mUsbAudioPermissionMessageHandler; @Override public void onCreate(Bundle icicle) { @@ -73,7 +75,9 @@ public class UsbPermissionActivity extends AlertActivity mUid = intent.getIntExtra(Intent.EXTRA_UID, -1); mPackageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE); boolean canBeDefault = intent.getBooleanExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, false); - + mUsbAudioPermissionMessageHandler = new UsbAudioWarningDialogMessage( + getApplicationContext(), getIntent(), + UsbAudioWarningDialogMessage.TYPE_PERMISSION); PackageManager packageManager = getPackageManager(); ApplicationInfo aInfo; try { @@ -91,8 +95,8 @@ public class UsbPermissionActivity extends AlertActivity if (mDevice == null) { // Accessory Case - ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName, - mAccessory.getDescription()); + final int messageId = mUsbAudioPermissionMessageHandler.getUsbAccessoryPromptId(); + ap.mMessage = getString(messageId, appName, mAccessory.getDescription()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); } else { boolean hasRecordPermission = @@ -103,10 +107,11 @@ public class UsbPermissionActivity extends AlertActivity boolean isAudioCaptureDevice = mDevice.getHasAudioCapture(); useRecordWarning = isAudioCaptureDevice && !hasRecordPermission; - int strID = useRecordWarning - ? R.string.usb_device_permission_prompt_warn - : R.string.usb_device_permission_prompt; - ap.mMessage = getString(strID, appName, mDevice.getProductName()); + final int messageId = mUsbAudioPermissionMessageHandler.getMessageId(); + final int titleId = mUsbAudioPermissionMessageHandler.getPromptTitleId(); + ap.mTitle = getString(titleId, appName, mDevice.getProductName()); + ap.mMessage = (messageId != Resources.ID_NULL) ? getString(messageId, appName, + mDevice.getProductName()) : null; mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 6d8645e44fb0..086cb1ab68fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -51,6 +51,7 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -64,9 +65,6 @@ import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; -import java.util.Optional; -import java.util.function.Function; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -75,6 +73,11 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; +import java.util.function.Function; + +import dagger.Lazy; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest @@ -103,6 +106,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback; + private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -144,7 +148,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mKeyguardStateController, () -> mKeyguardUnlockAnimationController, mUnlockedScreenOffAnimationController, - () -> mNotificationShadeDepthController); + () -> mNotificationShadeDepthController, + mNotificationShadeWindowControllerLazy); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 13989d3f0ebe..5eaa60a6f157 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -23,7 +23,6 @@ import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; -import static com.android.systemui.statusbar.notification.ViewGroupFadeHelper.reset; import static com.google.common.truth.Truth.assertThat; @@ -107,6 +106,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarStateControllerImpl; @@ -305,6 +305,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private NotificationsQSContainerController mNotificationsQSContainerController; @Mock + private NotificationShadeWindowController mNotificationShadeWindowController; + @Mock private FeatureFlags mFeatureFlags; private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); private SysuiStatusBarStateController mStatusBarStateController; @@ -402,8 +404,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean())) .thenReturn(mKeyguardBottomArea); when(mNotificationRemoteInputManager.isRemoteInputActive()).thenReturn(false); - - reset(mView); + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); mNotificationPanelViewController = new NotificationPanelViewController(mView, mResources, @@ -412,8 +416,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController, mFalsingManager, new FalsingCollectorFake(), mNotificationLockscreenUserManager, mNotificationEntryManager, - mKeyguardStateController, mStatusBarStateController, mDozeLog, - mDozeParameters, mCommandQueue, mVibratorHelper, + mKeyguardStateController, mStatusBarStateController, + mNotificationShadeWindowController, + mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper, mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor, mMetricsLogger, mActivityManager, mConfigurationController, () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index 90b8a74d88be..cb468108880a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -225,4 +226,17 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) != 0).isTrue(); assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue(); } + + @Test + public void batchApplyWindowLayoutParams_doesNotDispatchEvents() { + mNotificationShadeWindowController.setForceDozeBrightness(true); + verify(mWindowManager).updateViewLayout(any(), any()); + + clearInvocations(mWindowManager); + mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { + mNotificationShadeWindowController.setForceDozeBrightness(false); + verify(mWindowManager, never()).updateViewLayout(any(), any()); + }); + verify(mWindowManager).updateViewLayout(any(), any()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 20575ae504ad..2709e436a663 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -351,6 +351,10 @@ public class StatusBarTest extends SysuiTestCase { when(mStatusBarComponentFactory.create()).thenReturn(mStatusBarComponent); when(mStatusBarComponent.getNotificationShadeWindowViewController()).thenReturn( mNotificationShadeWindowViewController); + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); mShadeController = new ShadeControllerImpl(mCommandQueue, mStatusBarStateController, mNotificationShadeWindowController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index b385b7d62cff..45c6be936eb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -22,7 +22,6 @@ import android.app.IUidObserver import android.app.Notification import android.app.PendingIntent import android.app.Person -import android.content.Intent import android.service.notification.NotificationListenerService.REASON_USER_STOPPED import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -429,6 +428,19 @@ class OngoingCallControllerTest : SysuiTestCase() { .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id) } + /** Regression test for b/212467440. */ + @Test + fun chipClicked_activityStarterTriggeredWithUnmodifiedIntent() { + val notifEntry = createOngoingCallNotifEntry() + val pendingIntent = notifEntry.sbn.notification.contentIntent + notifCollectionListener.onEntryUpdated(notifEntry) + + chipView.performClick() + + // Ensure that the sysui didn't modify the notification's intent -- see b/212467440. + verify(mockActivityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any()) + } + @Test fun notifyChipVisibilityChanged_visibleEventLogged() { controller.notifyChipVisibilityChanged(true) @@ -570,7 +582,6 @@ class OngoingCallControllerTest : SysuiTestCase() { notificationEntryBuilder.modifyNotification(context).setContentIntent(null) } else { val contentIntent = mock(PendingIntent::class.java) - `when`(contentIntent.intent).thenReturn(mock(Intent::class.java)) notificationEntryBuilder.modifyNotification(context).setContentIntent(contentIntent) } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 330d2ddc0a94..ed7448140581 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13804,14 +13804,26 @@ public class ActivityManagerService extends IActivityManager.Stub return false; } - if (!Build.IS_DEBUGGABLE) { - int match = mContext.getPackageManager().checkSignatures( - ii.targetPackage, ii.packageName); - if (match < 0 && match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) { + int match = mContext.getPackageManager().checkSignatures( + ii.targetPackage, ii.packageName); + if (match < 0 && match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) { + if (Build.IS_DEBUGGABLE) { + String message = "Instrumentation test " + ii.packageName + + " doesn't have a signature matching the target " + + ii.targetPackage + + ", which would not be allowed on the production Android builds"; + if (callingUid != Process.ROOT_UID) { + Slog.e(TAG, message + + ". THIS WILL BE DISALLOWED ON FUTURE ANDROID VERSIONS" + + " unless from a rooted ADB shell."); + } else { + Slog.w(TAG, message); + } + } else { String msg = "Permission Denial: starting instrumentation " + className + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + " not allowed because package " + ii.packageName + " does not have a signature matching the target " + ii.targetPackage; diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index b47ea4f7a4b8..399ca0326313 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -176,6 +176,7 @@ public class SpatializerHelper { */ synchronized void reset(boolean featureEnabled) { Log.i(TAG, "Resetting"); + releaseSpat(); mState = STATE_UNINITIALIZED; mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; @@ -517,10 +518,10 @@ public class SpatializerHelper { try { mSpat.registerHeadTrackingCallback(null); mSpat.release(); - mSpat = null; } catch (RemoteException e) { Log.e(TAG, "Can't set release spatializer cleanly", e); } + mSpat = null; } } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index b6b54fc19011..c3e0290fb530 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -200,6 +200,10 @@ public final class NotificationRecord { private boolean mIsAppImportanceLocked; private ArraySet<Uri> mGrantableUris; + // Storage for phone numbers that were found to be associated with + // contacts in this notification. + private ArraySet<String> mPhoneNumbers; + // Whether this notification record should have an update logged the next time notifications // are sorted. private boolean mPendingLogUpdate = false; @@ -1525,6 +1529,26 @@ public final class NotificationRecord { return mPendingLogUpdate; } + /** + * Merge the given set of phone numbers into the list of phone numbers that + * are cached on this notification record. + */ + public void mergePhoneNumbers(ArraySet<String> phoneNumbers) { + // if the given phone numbers are null or empty then don't do anything + if (phoneNumbers == null || phoneNumbers.size() == 0) { + return; + } + // initialize if not already + if (mPhoneNumbers == null) { + mPhoneNumbers = new ArraySet<>(); + } + mPhoneNumbers.addAll(phoneNumbers); + } + + public ArraySet<String> getPhoneNumbers() { + return mPhoneNumbers; + } + @VisibleForTesting static final class Light { public final int color; diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index d7bc3bb8af28..dc4d04feab72 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -68,7 +68,10 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { private static final boolean ENABLE_PEOPLE_VALIDATOR = true; private static final String SETTING_ENABLE_PEOPLE_VALIDATOR = "validate_notification_people_enabled"; - private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.STARRED }; + private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.LOOKUP_KEY, + Contacts.STARRED, Contacts.HAS_PHONE_NUMBER }; + private static final String[] PHONE_LOOKUP_PROJECTION = + { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }; private static final int MAX_PEOPLE = 10; private static final int PEOPLE_CACHE_SIZE = 200; @@ -409,6 +412,35 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return lookupResult; } + @VisibleForTesting + // Performs a contacts search using searchContacts, and then follows up by looking up + // any phone numbers associated with the resulting contact information and merge those + // into the lookup result as well. Will have no additional effect if the contact does + // not have any phone numbers. + LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) { + LookupResult lookupResult = searchContacts(context, lookupUri); + String phoneLookupKey = lookupResult.getPhoneLookupKey(); + if (phoneLookupKey != null) { + String selection = Contacts.LOOKUP_KEY + " = ?"; + String[] selectionArgs = new String[] { phoneLookupKey }; + try (Cursor cursor = context.getContentResolver().query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION, + selection, selectionArgs, /* sortOrder= */ null)) { + if (cursor == null) { + Slog.w(TAG, "Cursor is null when querying contact phone number."); + return lookupResult; + } + + while (cursor.moveToNext()) { + lookupResult.mergePhoneNumber(cursor); + } + } catch (Throwable t) { + Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t); + } + } + return lookupResult; + } + private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) { final int workUserId = findWorkUserId(context); if (workUserId == -1) { @@ -454,6 +486,9 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { private final long mExpireMillis; private float mAffinity = NONE; + private boolean mHasPhone = false; + private String mPhoneLookupKey = null; + private ArraySet<String> mPhoneNumbers = new ArraySet<>(); public LookupResult() { mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS; @@ -473,6 +508,15 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { Slog.i(TAG, "invalid cursor: no _ID"); } + // Lookup key for potentially looking up contact phone number later + final int lookupKeyIdx = cursor.getColumnIndex(Contacts.LOOKUP_KEY); + if (lookupKeyIdx >= 0) { + mPhoneLookupKey = cursor.getString(lookupKeyIdx); + if (DEBUG) Slog.d(TAG, "contact LOOKUP_KEY is: " + mPhoneLookupKey); + } else { + if (DEBUG) Slog.d(TAG, "invalid cursor: no LOOKUP_KEY"); + } + // Starred final int starIdx = cursor.getColumnIndex(Contacts.STARRED); if (starIdx >= 0) { @@ -484,6 +528,39 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } else { if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED"); } + + // whether a phone number is present + final int hasPhoneIdx = cursor.getColumnIndex(Contacts.HAS_PHONE_NUMBER); + if (hasPhoneIdx >= 0) { + mHasPhone = cursor.getInt(hasPhoneIdx) != 0; + if (DEBUG) Slog.d(TAG, "contact HAS_PHONE_NUMBER is: " + mHasPhone); + } else { + if (DEBUG) Slog.d(TAG, "invalid cursor: no HAS_PHONE_NUMBER"); + } + } + + // Returns the phone lookup key that is cached in this result, or null + // if the contact has no known phone info. + public String getPhoneLookupKey() { + if (!mHasPhone) { + return null; + } + return mPhoneLookupKey; + } + + // Merge phone number found in this lookup and store it in mPhoneNumbers. + public void mergePhoneNumber(Cursor cursor) { + final int phoneNumIdx = cursor.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER); + if (phoneNumIdx >= 0) { + mPhoneNumbers.add(cursor.getString(phoneNumIdx)); + } else { + if (DEBUG) Slog.d(TAG, "invalid cursor: no NORMALIZED_NUMBER"); + } + } + + public ArraySet<String> getPhoneNumbers() { + return mPhoneNumbers; } private boolean isExpired() { @@ -509,6 +586,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { // Amount of time to wait for a result from the contacts db before rechecking affinity. private static final long LOOKUP_TIME = 1000; private float mContactAffinity = NONE; + private ArraySet<String> mPhoneNumbers = null; private NotificationRecord mRecord; private PeopleRankingReconsideration(Context context, String key, @@ -543,7 +621,9 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart()); } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) { if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle); - lookupResult = searchContacts(mContext, uri); + // only look up phone number if this is a contact lookup uri and thus isn't + // already directly a phone number. + lookupResult = searchContactsAndLookupNumbers(mContext, uri); } else { lookupResult = new LookupResult(); // invalid person for the cache if (!"name".equals(uri.getScheme())) { @@ -561,6 +641,13 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity()); } mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity()); + // merge any phone numbers found in this lookup result + if (lookupResult.getPhoneNumbers() != null) { + if (mPhoneNumbers == null) { + mPhoneNumbers = new ArraySet<>(); + } + mPhoneNumbers.addAll(lookupResult.getPhoneNumbers()); + } } else { if (DEBUG) Slog.d(TAG, "lookupResult is null"); } @@ -581,6 +668,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { float affinityBound = operand.getContactAffinity(); operand.setContactAffinity(Math.max(mContactAffinity, affinityBound)); if (VERBOSE) Slog.i(TAG, "final affinity: " + operand.getContactAffinity()); + operand.mergePhoneNumbers(mPhoneNumbers); } public float getContactAffinity() { diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index acbe04b8c519..f9a7c4658245 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -33,6 +33,7 @@ import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.messages.nano.SystemMessageProto; @@ -126,7 +127,7 @@ public class ZenModeFiltering { } protected void recordCall(NotificationRecord record) { - REPEAT_CALLERS.recordCall(mContext, extras(record)); + REPEAT_CALLERS.recordCall(mContext, extras(record), record.getPhoneNumbers()); } /** @@ -325,6 +326,10 @@ public class ZenModeFiltering { } } + protected void cleanUpCallersAfter(long timeThreshold) { + REPEAT_CALLERS.cleanUpCallsAfter(timeThreshold); + } + private static class RepeatCallers { // We keep a separate map per uri scheme to do more generous number-matching // handling on telephone numbers specifically. For other inputs, we @@ -333,7 +338,8 @@ public class ZenModeFiltering { private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>(); private int mThresholdMinutes; - private synchronized void recordCall(Context context, Bundle extras) { + private synchronized void recordCall(Context context, Bundle extras, + ArraySet<String> phoneNumbers) { setThresholdMinutes(context); if (mThresholdMinutes <= 0 || extras == null) return; final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); @@ -341,7 +347,7 @@ public class ZenModeFiltering { final long now = System.currentTimeMillis(); cleanUp(mTelCalls, now); cleanUp(mOtherCalls, now); - recordCallers(extraPeople, now); + recordCallers(extraPeople, phoneNumbers, now); } private synchronized boolean isRepeat(Context context, Bundle extras) { @@ -365,6 +371,23 @@ public class ZenModeFiltering { } } + // Clean up all calls that occurred after the given time. + // Used only for tests, to clean up after testing. + private synchronized void cleanUpCallsAfter(long timeThreshold) { + for (int i = mTelCalls.size() - 1; i >= 0; i--) { + final long time = mTelCalls.valueAt(i); + if (time > timeThreshold) { + mTelCalls.removeAt(i); + } + } + for (int j = mOtherCalls.size() - 1; j >= 0; j--) { + final long time = mOtherCalls.valueAt(j); + if (time > timeThreshold) { + mOtherCalls.removeAt(j); + } + } + } + private void setThresholdMinutes(Context context) { if (mThresholdMinutes <= 0) { mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer @@ -372,7 +395,8 @@ public class ZenModeFiltering { } } - private synchronized void recordCallers(String[] people, long now) { + private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers, + long now) { for (int i = 0; i < people.length; i++) { String person = people[i]; if (person == null) continue; @@ -393,6 +417,14 @@ public class ZenModeFiltering { mOtherCalls.put(person, now); } } + + // record any additional numbers from the notification record if + // provided; these are in the format of just a phone number string + if (phoneNumbers != null) { + for (String num : phoneNumbers) { + mTelCalls.put(num, now); + } + } } private synchronized boolean checkCallers(Context context, String[] people) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d0e445749698..3ddcf17d0a47 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -126,6 +126,7 @@ import android.system.StructStat; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.EventLog; import android.util.ExceptionUtils; import android.util.MathUtils; import android.util.Slog; @@ -3097,6 +3098,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (mResolvedBaseFile == null) { mResolvedBaseFile = new File(appInfo.getBaseCodePath()); inheritFileLocked(mResolvedBaseFile); + } else if ((params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) { + EventLog.writeEvent(0x534e4554, "219044664"); + + // Installing base.apk. Make sure the app is restarted. + params.setDontKillApp(false); } // Inherit splits if not overridden. @@ -3743,6 +3749,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override + public int getInstallFlags() { + return params.installFlags; + } + + @Override public DataLoaderParamsParcel getDataLoaderParams() { mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null); return params.dataLoaderParams != null ? params.dataLoaderParams.getData() : null; diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 8b4690629ec5..e5db2e4dab35 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -185,8 +185,13 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat initializeActivityRecognizersTags(); - // If this device does not have telephony, restrict the phone call ops - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + // If this device does not have telephony or microphone features, the TelecomService will + // not be started (which sets phone call ops to allow only its package). Therefore, phone + // call ops need to be restricted here. + PackageManager pm = mContext.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) + && !pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) + && !pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) { AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, mToken, null, UserHandle.USER_ALL); diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index ac650ec0f564..2029f869802e 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -195,6 +195,12 @@ public class KeyguardServiceWrapper implements IKeyguardService { @Override // Binder interface public void doKeyguardTimeout(Bundle options) { + int userId = mKeyguardStateMonitor.getCurrentUser(); + if (mKeyguardStateMonitor.isSecure(userId)) { + // Preemptively inform the cache that the keyguard will soon be showing, as calls to + // doKeyguardTimeout are a signal to lock the device as soon as possible. + mKeyguardStateMonitor.onShowingStateChanged(true, userId); + } try { mService.doKeyguardTimeout(options); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index e6511372d62c..c0aa8aeff711 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -78,8 +78,14 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { return mTrusted; } + public int getCurrentUser() { + return mCurrentUserId; + } + @Override // Binder interface - public void onShowingStateChanged(boolean showing) { + public void onShowingStateChanged(boolean showing, int userId) { + if (userId != mCurrentUserId) return; + mIsShowing = showing; mCallback.onShowingChanged(); diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index ee0e5ba916b9..e3dcfd0c89c0 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -247,6 +247,8 @@ public class SliceManagerService extends ISliceManager.Stub { if (autoGrantPermissions != null && callingPkg != null) { // Need to own the Uri to call in with permissions to grant. enforceOwner(callingPkg, uri, userId); + // b/208232850: Needs to verify caller before granting slice access + verifyCaller(callingPkg); for (String perm : autoGrantPermissions) { if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) { int providerUser = ContentProvider.getUserIdFromUri(uri, userId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 6fda72e1267b..eddb5e977fa2 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -59,8 +59,8 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; import android.graphics.Color; +import android.graphics.ImageDecoder; import android.graphics.Rect; import android.graphics.RectF; import android.hardware.display.DisplayManager; @@ -193,6 +193,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig"; static final String WALLPAPER_LOCK_CROP = "wallpaper_lock"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; + private static final String RECORD_FILE = "decode_record"; + private static final String RECORD_LOCK_FILE = "decode_lock_record"; // All the various per-user state files we need to be aware of private static final String[] sPerUserFiles = new String[] { @@ -674,8 +676,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (DEBUG) { - // This is just a quick estimation, may be smaller than it is. - long estimateSize = options.outWidth * options.outHeight * 4; + long estimateSize = (long) options.outWidth * options.outHeight * 4; Slog.v(TAG, "Null crop of new wallpaper, estimate size=" + estimateSize + ", success=" + success); } @@ -684,9 +685,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub FileOutputStream f = null; BufferedOutputStream bos = null; try { - BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance( - wallpaper.wallpaperFile.getAbsolutePath(), false); - // This actually downsamples only by powers of two, but that's okay; we do // a proper scaling blit later. This is to minimize transient RAM use. // We calculate the largest power-of-two under the actual ratio rather than @@ -740,8 +738,24 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize()); } - Bitmap cropped = decoder.decodeRegion(cropHint, options); - decoder.recycle(); + //Create a record file and will delete if ImageDecoder work well. + final String recordName = + (wallpaper.wallpaperFile.getName().equals(WALLPAPER) + ? RECORD_FILE : RECORD_LOCK_FILE); + final File record = new File(getWallpaperDir(wallpaper.userId), recordName); + record.createNewFile(); + Slog.v(TAG, "record path =" + record.getPath() + + ", record name =" + record.getName()); + + final ImageDecoder.Source srcData = + ImageDecoder.createSource(wallpaper.wallpaperFile); + final int sampleSize = scale; + Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> { + decoder.setTargetSampleSize(sampleSize); + decoder.setCrop(estimateCrop); + }); + + record.delete(); if (cropped == null) { Slog.e(TAG, "Could not decode new wallpaper"); @@ -1779,6 +1793,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub new UserSwitchObserver() { @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) { + errorCheck(newUserId); switchUser(newUserId, reply); } }, TAG); @@ -1816,6 +1831,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub @Override public void onBootPhase(int phase) { + // If someone set too large jpg file as wallpaper, system_server may be killed by lmk in + // generateCrop(), so we create a file in generateCrop() before ImageDecoder starts working + // and delete this file after ImageDecoder finishing. If the specific file exists, that + // means ImageDecoder can't handle the original wallpaper file, in order to avoid + // system_server restart again and again and rescue party will trigger factory reset, + // so we reset default wallpaper in case system_server is trapped into a restart loop. + errorCheck(UserHandle.USER_SYSTEM); + if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { systemReady(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { @@ -1823,6 +1846,38 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + private static final HashMap<Integer, String> sWallpaperType = new HashMap<Integer, String>() { + { + put(FLAG_SYSTEM, RECORD_FILE); + put(FLAG_LOCK, RECORD_LOCK_FILE); + } + }; + + private void errorCheck(int userID) { + sWallpaperType.forEach((type, filename) -> { + final File record = new File(getWallpaperDir(userID), filename); + if (record.exists()) { + Slog.w(TAG, "User:" + userID + ", wallpaper tyep = " + type + + ", wallpaper fail detect!! reset to default wallpaper"); + clearWallpaperData(userID, type); + record.delete(); + } + }); + } + + private void clearWallpaperData(int userID, int wallpaperType) { + final WallpaperData wallpaper = new WallpaperData(userID, getWallpaperDir(userID), + (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER, + (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP); + if (wallpaper.sourceExists()) { + wallpaper.wallpaperFile.delete(); + } + if (wallpaper.cropExists()) { + wallpaper.cropFile.delete(); + } + + } + @Override public void onUnlockUser(final int userId) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index badb1f5a0a12..4708d0026931 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -97,7 +97,7 @@ class EnsureActivitiesVisibleHelper { // activities are actually behind other fullscreen activities, but still required // to be visible (such as performing Recents animation). final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind - && mTaskFragment.isTopActivityFocusable() + && mTaskFragment.canBeResumed(starting) && (starting == null || !starting.isDescendantOf(mTaskFragment)); ArrayList<TaskFragment> adjacentTaskFragments = null; diff --git a/services/core/java/com/android/server/wm/MirrorActiveUids.java b/services/core/java/com/android/server/wm/MirrorActiveUids.java index 4e7f1d4cca18..b9aa9599babe 100644 --- a/services/core/java/com/android/server/wm/MirrorActiveUids.java +++ b/services/core/java/com/android/server/wm/MirrorActiveUids.java @@ -19,7 +19,7 @@ package com.android.server.wm; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import android.app.ActivityManager.ProcessState; -import android.util.SparseArray; +import android.util.SparseIntArray; import java.io.PrintWriter; @@ -29,15 +29,14 @@ import java.io.PrintWriter; * adjustment) or getting state from window manager (background start check). */ class MirrorActiveUids { - private final SparseArray<UidRecord> mUidStates = new SparseArray<>(); + /** Uid -> process state. */ + private final SparseIntArray mUidStates = new SparseIntArray(); + + /** Uid -> number of non-app visible windows belong to the uid. */ + private final SparseIntArray mNumNonAppVisibleWindowMap = new SparseIntArray(); synchronized void onUidActive(int uid, int procState) { - UidRecord r = mUidStates.get(uid); - if (r == null) { - r = new UidRecord(); - mUidStates.put(uid, r); - } - r.mProcState = procState; + mUidStates.put(uid, procState); } synchronized void onUidInactive(int uid) { @@ -45,22 +44,28 @@ class MirrorActiveUids { } synchronized void onUidProcStateChanged(int uid, int procState) { - final UidRecord r = mUidStates.get(uid); - if (r != null) { - r.mProcState = procState; + final int index = mUidStates.indexOfKey(uid); + if (index >= 0) { + mUidStates.setValueAt(index, procState); } } synchronized @ProcessState int getUidState(int uid) { - final UidRecord r = mUidStates.get(uid); - return r != null ? r.mProcState : PROCESS_STATE_NONEXISTENT; + return mUidStates.get(uid, PROCESS_STATE_NONEXISTENT); } /** Called when the surface of non-application (exclude toast) window is shown or hidden. */ synchronized void onNonAppSurfaceVisibilityChanged(int uid, boolean visible) { - final UidRecord r = mUidStates.get(uid); - if (r != null) { - r.mNumNonAppVisibleWindow += visible ? 1 : -1; + final int index = mNumNonAppVisibleWindowMap.indexOfKey(uid); + if (index >= 0) { + final int num = mNumNonAppVisibleWindowMap.valueAt(index) + (visible ? 1 : -1); + if (num > 0) { + mNumNonAppVisibleWindowMap.setValueAt(index, num); + } else { + mNumNonAppVisibleWindowMap.removeAt(index); + } + } else if (visible) { + mNumNonAppVisibleWindowMap.append(uid, 1); } } @@ -70,23 +75,15 @@ class MirrorActiveUids { * {@link VisibleActivityProcessTracker}. */ synchronized boolean hasNonAppVisibleWindow(int uid) { - final UidRecord r = mUidStates.get(uid); - return r != null && r.mNumNonAppVisibleWindow > 0; + return mNumNonAppVisibleWindowMap.get(uid) > 0; } synchronized void dump(PrintWriter pw, String prefix) { - pw.print(prefix + "NumNonAppVisibleWindowByUid:["); - for (int i = mUidStates.size() - 1; i >= 0; i--) { - final UidRecord r = mUidStates.valueAt(i); - if (r.mNumNonAppVisibleWindow > 0) { - pw.print(" " + mUidStates.keyAt(i) + ":" + r.mNumNonAppVisibleWindow); - } + pw.print(prefix + "NumNonAppVisibleWindowUidMap:["); + for (int i = mNumNonAppVisibleWindowMap.size() - 1; i >= 0; i--) { + pw.print(" " + mNumNonAppVisibleWindowMap.keyAt(i) + ":" + + mNumNonAppVisibleWindowMap.valueAt(i)); } pw.println("]"); } - - private static final class UidRecord { - @ProcessState int mProcState; - int mNumNonAppVisibleWindow; - } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index fbc8f73b53b0..9cd37b305dc6 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1979,7 +1979,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> try { if (mTaskSupervisor.realStartActivityLocked(r, app, - top == r && r.isFocusable() /*andResume*/, true /*checkConfig*/)) { + top == r && r.getTask().canBeResumed(r) /*andResume*/, + true /*checkConfig*/)) { mTmpBoolean = true; } } catch (RemoteException e) { @@ -3623,11 +3624,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return new ArrayList<>(); } } else { + final RecentTasks recentTasks = mWindowManager.mAtmService.getRecentTasks(); + final int recentsComponentUid = recentTasks != null + ? recentTasks.getRecentsComponentUid() + : -1; final ArrayList<ActivityRecord> activities = new ArrayList<>(); - forAllRootTasks(rootTask -> { - if (!dumpVisibleRootTasksOnly || rootTask.shouldBeVisible(null)) { - activities.addAll(rootTask.getDumpActivitiesLocked(name)); + forAllLeafTasks(task -> { + final boolean isRecents = (task.effectiveUid == recentsComponentUid); + if (!dumpVisibleRootTasksOnly || task.shouldBeVisible(null) || isRecents) { + activities.addAll(task.getDumpActivitiesLocked(name)); } + return false; }); return activities; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 24b66ca3ac9c..ae7e96fab1fe 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -822,11 +822,17 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!adjacentTaskFragments.isEmpty() && !gotTranslucentAdjacent) { // The z-order of this TaskFragment is in middle of two adjacent TaskFragments // and it cannot be visible if the TaskFragment on top is not translucent and - // is fully occluding this one. + // is occluding this one. + mTmpRect.set(getBounds()); for (int j = adjacentTaskFragments.size() - 1; j >= 0; --j) { final TaskFragment taskFragment = adjacentTaskFragments.get(j); - if (!taskFragment.isTranslucent(starting) - && taskFragment.getBounds().contains(this.getBounds())) { + final TaskFragment adjacentTaskFragment = + taskFragment.mAdjacentTaskFragment; + if (adjacentTaskFragment == this) { + continue; + } + if (mTmpRect.intersect(taskFragment.getBounds()) + || mTmpRect.intersect(adjacentTaskFragment.getBounds())) { return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4258e073429e..575ae691dbe8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3277,9 +3277,6 @@ public class WindowManagerService extends IWindowManager.Stub if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) { throw new SecurityException("Requires CONTROL_KEYGUARD permission"); } - if (mAtmInternal.isDreaming()) { - mAtmService.mTaskSupervisor.wakeUp("dismissKeyguard"); - } synchronized (mGlobalLock) { mPolicy.dismissKeyguardLw(callback, message); } diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index a94fd074ff2e..2d8410bd12d2 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -513,9 +513,14 @@ public class WindowManagerShellCommand extends ShellCommand { try (ZipOutputStream out = new ZipOutputStream(getRawOutputStream())) { ArrayList<Pair<String, ByteTransferPipe>> requestList = new ArrayList<>(); synchronized (mInternal.mGlobalLock) { + final RecentTasks recentTasks = mInternal.mAtmService.getRecentTasks(); + final int recentsComponentUid = recentTasks != null + ? recentTasks.getRecentsComponentUid() + : -1; // Request dump from all windows parallelly before writing to disk. mInternal.mRoot.forAllWindows(w -> { - if (w.isVisible()) { + final boolean isRecents = (w.mSession.mUid == recentsComponentUid); + if (w.isVisible() || isRecents) { ByteTransferPipe pipe = null; try { pipe = new ByteTransferPipe(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index b89d116f4fe4..0fbcfd3a3ba1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -8465,20 +8465,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { admin.getPackageName(), userId, "set-device-owner"); Slogf.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId); + } - if (setProfileOwnerOnCurrentUserIfNecessary - && mInjector.userManagerIsHeadlessSystemUserMode()) { - int currentForegroundUser = getCurrentForegroundUserId(); - Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin - + " as profile owner on user " + currentForegroundUser); - // Sets profile owner on current foreground user since - // the human user will complete the DO setup workflow from there. - manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin, - /* managedUser= */ currentForegroundUser, /* adminExtras= */ null, - /* showDisclaimer= */ false); + if (setProfileOwnerOnCurrentUserIfNecessary + && mInjector.userManagerIsHeadlessSystemUserMode()) { + int currentForegroundUser; + synchronized (getLockObject()) { + currentForegroundUser = getCurrentForegroundUserId(); } - return true; + Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin + + " as profile owner on user " + currentForegroundUser); + // Sets profile owner on current foreground user since + // the human user will complete the DO setup workflow from there. + manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin, + /* managedUser= */ currentForegroundUser, /* adminExtras= */ null, + /* showDisclaimer= */ false); } + return true; } @Override diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 49b63863fec7..dfd8f48460c0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -66,6 +66,7 @@ import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; +import android.util.ArraySet; import android.widget.RemoteViews; import androidx.test.filters.SmallTest; @@ -1328,4 +1329,45 @@ public class NotificationRecordTest extends UiServiceTestCase { assertFalse(record.isConversation()); } + + @Test + public void mergePhoneNumbers_nulls() { + // make sure nothing dies if we just don't have any phone numbers + StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, null /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); + + // by default, no phone numbers + assertNull(record.getPhoneNumbers()); + + // nothing happens if we attempt to merge phone numbers but there aren't any + record.mergePhoneNumbers(null); + assertNull(record.getPhoneNumbers()); + } + + @Test + public void mergePhoneNumbers_addNumbers() { + StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, null /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); + + // by default, no phone numbers + assertNull(record.getPhoneNumbers()); + + // make sure it behaves properly when we merge in some real content + record.mergePhoneNumbers(new ArraySet<>( + new String[]{"16175551212", "16175552121"})); + assertTrue(record.getPhoneNumbers().contains("16175551212")); + assertTrue(record.getPhoneNumbers().contains("16175552121")); + assertFalse(record.getPhoneNumbers().contains("16175553434")); + + // now merge in a new number, make sure old ones are still there and the new one + // is also there + record.mergePhoneNumbers(new ArraySet<>(new String[]{"16175553434"})); + assertTrue(record.getPhoneNumbers().contains("16175551212")); + assertTrue(record.getPhoneNumbers().contains("16175552121")); + assertTrue(record.getPhoneNumbers().contains("16175553434")); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java index 0bf105d62053..0552a8350329 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java @@ -19,8 +19,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -29,6 +34,7 @@ import android.app.Person; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.UserManager; @@ -43,6 +49,8 @@ import com.android.server.UiServiceTestCase; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; @@ -240,6 +248,118 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { assertFalse(ContentProvider.uriHasUserId(queryUri.getValue())); } + @Test + public void testMergePhoneNumbers_noPhoneNumber() { + // If merge phone number is called but the contacts lookup turned up no available + // phone number (HAS_PHONE_NUMBER is false), then no query should happen. + + // setup of various bits required for querying + final Context mockContext = mock(Context.class); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + final int contactId = 12345; + final Uri lookupUri = Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId)); + + // when the contact is looked up, we return a cursor that has one entry whose info is: + // _ID: 1 + // LOOKUP_KEY: "testlookupkey" + // STARRED: 0 + // HAS_PHONE_NUMBER: 0 + Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 0); + when(mockContentResolver.query(any(), any(), any(), any(), any())).thenReturn(cursor); + + // call searchContacts and then mergePhoneNumbers, make sure we never actually + // query the content resolver for a phone number + new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri); + verify(mockContentResolver, never()).query( + eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI), + eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }), + contains(ContactsContract.Contacts.LOOKUP_KEY), + any(), // selection args + isNull()); // sort order + } + + @Test + public void testMergePhoneNumbers_hasNumber() { + // If merge phone number is called and the contact lookup has a phone number, + // make sure there's then a subsequent query for the phone number. + + // setup of various bits required for querying + final Context mockContext = mock(Context.class); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + final int contactId = 12345; + final Uri lookupUri = Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId)); + + // when the contact is looked up, we return a cursor that has one entry whose info is: + // _ID: 1 + // LOOKUP_KEY: "testlookupkey" + // STARRED: 0 + // HAS_PHONE_NUMBER: 1 + Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 1); + + // make sure to add some specifics so this cursor is only returned for the + // contacts database lookup. + when(mockContentResolver.query(eq(lookupUri), any(), + isNull(), isNull(), isNull())).thenReturn(cursor); + + // in the case of a phone lookup, return null cursor; that's not an error case + // and we're not checking the actual storing of the phone data here. + when(mockContentResolver.query(eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI), + eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }), + contains(ContactsContract.Contacts.LOOKUP_KEY), + any(), isNull())).thenReturn(null); + + // call searchContacts and then mergePhoneNumbers, and check that we query + // once for the + new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri); + verify(mockContentResolver, times(1)).query( + eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI), + eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }), + contains(ContactsContract.Contacts.LOOKUP_KEY), + eq(new String[] { "testlookupkey" }), // selection args + isNull()); // sort order + } + + // Creates a cursor that points to one item of Contacts data with the specified + // columns. + private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) { + Cursor mockCursor = mock(Cursor.class); + when(mockCursor.moveToFirst()).thenReturn(true); + doAnswer(new Answer<Boolean>() { + boolean mAccessed = false; + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + if (!mAccessed) { + mAccessed = true; + return true; + } + return false; + } + + }).when(mockCursor).moveToNext(); + + // id + when(mockCursor.getColumnIndex(ContactsContract.Contacts._ID)).thenReturn(0); + when(mockCursor.getInt(0)).thenReturn(id); + + // lookup key + when(mockCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)).thenReturn(1); + when(mockCursor.getString(1)).thenReturn(lookupKey); + + // starred + when(mockCursor.getColumnIndex(ContactsContract.Contacts.STARRED)).thenReturn(2); + when(mockCursor.getInt(2)).thenReturn(starred); + + // has phone number + when(mockCursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)).thenReturn(3); + when(mockCursor.getInt(3)).thenReturn(hasPhone); + + return mockCursor; + } + private void assertStringArrayEquals(String message, String[] expected, String[] result) { String expectedString = Arrays.toString(expected); String resultString = Arrays.toString(result); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java index 0f18cc61a95a..abcc8c1e99cb 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java @@ -50,11 +50,13 @@ import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.ArraySet; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.util.NotificationMessagingUtil; import com.android.server.UiServiceTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -72,6 +74,8 @@ public class ZenModeFilteringTest extends UiServiceTestCase { @Mock private TelephonyManager mTelephonyManager; + private long mTestStartTime; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -79,6 +83,13 @@ public class ZenModeFilteringTest extends UiServiceTestCase { // for repeat callers / matchesCallFilter mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager); + mTestStartTime = System.currentTimeMillis(); + } + + @After + public void tearDown() { + // make sure to get rid of any data stored in repeat callers + mZenModeFiltering.cleanUpCallersAfter(mTestStartTime); } private NotificationRecord getNotificationRecord() { @@ -108,7 +119,10 @@ public class ZenModeFilteringTest extends UiServiceTestCase { return extras; } - private NotificationRecord getNotificationRecordWithPeople(String[] people) { + // Create a notification record with the people String array as the + // bundled extras, and the numbers ArraySet as additional phone numbers. + private NotificationRecord getRecordWithPeopleInfo(String[] people, + ArraySet<String> numbers) { // set up notification record NotificationRecord r = mock(NotificationRecord.class); StatusBarNotification sbn = mock(StatusBarNotification.class); @@ -116,6 +130,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { notification.extras = makeExtrasBundleWithPeople(people); when(sbn.getNotification()).thenReturn(notification); when(r.getSbn()).thenReturn(sbn); + when(r.getPhoneNumbers()).thenReturn(numbers); return r; } @@ -339,7 +354,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { // after calls given an email with an exact string match, make sure that // matchesCallFilter returns the right thing String[] mailSource = new String[]{"mailto:hello.world"}; - mZenModeFiltering.recordCall(getNotificationRecordWithPeople(mailSource)); + mZenModeFiltering.recordCall(getRecordWithPeopleInfo(mailSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -362,7 +377,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); String[] telSource = new String[]{"tel:+1-617-555-1212"}; - mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource)); + mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -406,7 +421,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); String[] telSource = new String[]{"tel:%2B16175551212"}; - mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource)); + mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -419,25 +434,64 @@ public class ZenModeFilteringTest extends UiServiceTestCase { Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"}); Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"}); - assertTrue("same number should match", + assertTrue("same number 1 should match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, same1, null, 0, 0)); - assertTrue("same number should match", + assertTrue("same number 2 should match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, same2, null, 0, 0)); - assertTrue("same number should match", + assertTrue("same number 3 should match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, same3, null, 0, 0)); - assertFalse("different number should not match", + assertFalse("different number 1 should not match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, different1, null, 0, 0)); - assertFalse("different number should not match", + assertFalse("different number 2 should not match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, different2, null, 0, 0)); } + + @Test + public void testMatchesCallFilter_repeatCallers_viaRecordPhoneNumbers() { + // make sure that phone numbers that are passed in via the NotificationRecord's + // cached phone numbers field (from a contact lookup if the record is provided a contact + // uri) also get recorded in the repeat callers list. + + // set up telephony manager behavior + when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); + + String[] contactSource = new String[]{"content://contacts/lookup/uri-here"}; + ArraySet<String> contactNumbers = new ArraySet<>( + new String[]{"1-617-555-1212", "1-617-555-3434"}); + NotificationRecord record = getRecordWithPeopleInfo(contactSource, contactNumbers); + record.mergePhoneNumbers(contactNumbers); + mZenModeFiltering.recordCall(record); + + // set up policy to only allow repeat callers + Policy policy = new Policy( + PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE); + + // both phone numbers should register here + Bundle tel1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"}); + Bundle tel2 = makeExtrasBundleWithPeople(new String[]{"tel:16175553434"}); + Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:16175555656"}); + + assertTrue("contact number 1 should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + tel1, null, 0, 0)); + assertTrue("contact number 2 should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + tel2, null, 0, 0)); + assertFalse("different number should not match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + different, null, 0, 0)); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index a91298f73d08..10011fd4e6e3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -31,7 +31,6 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -42,7 +41,6 @@ import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -158,16 +156,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void testDismissKeyguardCanWakeUp() { - doReturn(true).when(mWm).checkCallingPermission(anyString(), anyString()); - spyOn(mWm.mAtmInternal); - doReturn(true).when(mWm.mAtmInternal).isDreaming(); - doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString()); - mWm.dismissKeyguard(null, "test-dismiss-keyguard"); - verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString()); - } - - @Test public void testMoveWindowTokenToDisplay_NullToken_DoNothing() { mWm.moveWindowTokenToDisplay(null, mDisplayContent.getDisplayId()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index e6ad68aafaec..7d501356d469 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -800,7 +800,6 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testHasActiveVisibleWindow() { final int uid = ActivityBuilder.DEFAULT_FAKE_UID; - mAtm.mActiveUids.onUidActive(uid, 0 /* any proc state */); final WindowState app = createWindow(null, TYPE_APPLICATION, "app", uid); app.mActivityRecord.setVisible(false); @@ -828,6 +827,11 @@ public class WindowStateTests extends WindowTestsBase { // Make the application overlay window visible. It should be a valid active visible window. overlay.onSurfaceShownChanged(true); assertTrue(mAtm.hasActiveVisibleWindow(uid)); + + // The number of windows should be independent of the existence of uid state. + mAtm.mActiveUids.onUidInactive(uid); + mAtm.mActiveUids.onUidActive(uid, 0 /* any proc state */); + assertTrue(mAtm.mActiveUids.hasNonAppVisibleWindow(uid)); } @UseTestDisplay(addWindows = W_ACTIVITY) |