diff options
235 files changed, 7297 insertions, 1711 deletions
diff --git a/Android.bp b/Android.bp index d52f08f89038..731b9f617158 100644 --- a/Android.bp +++ b/Android.bp @@ -82,6 +82,7 @@ filegroup { ":framework-mca-filterpacks-sources", ":framework-media-sources", ":framework-mms-sources", + ":framework-omapi-sources", ":framework-opengl-sources", ":framework-rs-sources", ":framework-sax-sources", @@ -273,6 +274,7 @@ java_library { "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", "android.hardware.vibrator-V2-java", + "android.se.omapi-V1-java", "android.system.suspend.control.internal-java", "devicepolicyprotosnano", diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 59f602c11247..af4053fb6c8c 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -1576,7 +1576,7 @@ bool BootAnimation::playAnimation(const Animation& animation) { int err; do { err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, nullptr); - } while (err<0 && errno == EINTR); + } while (err == EINTR); } checkExit(); diff --git a/core/api/current.txt b/core/api/current.txt index b9ade0b442ba..32865ad929e9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3255,6 +3255,28 @@ package android.accessibilityservice { method public boolean willContinue(); } + public final class MagnificationConfig implements android.os.Parcelable { + method public int describeContents(); + method public float getCenterX(); + method public float getCenterY(); + method public int getMode(); + method public float getScale(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR; + field public static final int DEFAULT_MODE = 0; // 0x0 + field public static final int FULLSCREEN_MODE = 1; // 0x1 + field public static final int WINDOW_MODE = 2; // 0x2 + } + + public static final class MagnificationConfig.Builder { + ctor public MagnificationConfig.Builder(); + method @NonNull public android.accessibilityservice.MagnificationConfig build(); + method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float); + method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float); + method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int); + method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(float); + } + public final class TouchInteractionController { method public int getDisplayId(); method public int getMaxPointerCount(); @@ -27638,6 +27660,7 @@ package android.nfc.cardemulation { field public static final String CATEGORY_PAYMENT = "payment"; field public static final String EXTRA_CATEGORY = "category"; field public static final String EXTRA_SERVICE_COMPONENT = "component"; + field public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID"; field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1 field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2 field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0 @@ -40677,7 +40700,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(); method public String getDefaultDialerPackage(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String); - method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle); method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccountHandle getSimCallManager(); @@ -42745,7 +42768,7 @@ package android.telephony { method @Nullable public String getMccString(); method @Deprecated public int getMnc(); method @Nullable public String getMncString(); - method public String getNumber(); + method @Deprecated public String getNumber(); method public int getPortIndex(); method public int getSimSlotIndex(); method public int getSubscriptionId(); @@ -43000,7 +43023,7 @@ package android.telephony { method public String getIccAuthentication(int, int, String); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(int); - method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number(); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public String getManualNetworkSelectionPlmn(); method @Nullable public String getManufacturerCode(); method @Nullable public String getManufacturerCode(int); @@ -43085,7 +43108,7 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>); - method public boolean setLine1NumberForDisplay(String, String); + method @Deprecated public boolean setLine1NumberForDisplay(String, String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNetworkSelectionModeAutomatic(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(String, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(@NonNull String, boolean, int); @@ -51422,14 +51445,18 @@ package android.view.accessibility { public static final class AccessibilityNodeInfo.CollectionItemInfo { ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean); ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean, boolean); + ctor public AccessibilityNodeInfo.CollectionItemInfo(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean); method public int getColumnIndex(); method public int getColumnSpan(); + method @Nullable public String getColumnTitle(); method public int getRowIndex(); method public int getRowSpan(); + method @Nullable public String getRowTitle(); method @Deprecated public boolean isHeading(); method public boolean isSelected(); method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean); method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean); + method @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean); } public static final class AccessibilityNodeInfo.ExtraRenderingInfo { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 29a445322085..fb325a2c81d5 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -961,20 +961,20 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getDeviceOwnerNameOnAnyUser(); method @Nullable public CharSequence getDeviceOwnerOrganizationName(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; - method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserProvisioningState(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState(); method public boolean isDeviceManaged(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isManagedKiosk(); method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isUnattendedManagedKiosk(); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String); @@ -6365,14 +6365,14 @@ package android.media.tv.tuner.filter { } public class Filter implements java.lang.AutoCloseable { + method @Nullable public String acquireSharedFilterToken(); method public void close(); method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration); - method @Nullable public String createSharedFilter(); method public int flush(); + method public void freeSharedFilterToken(@NonNull String); method @Deprecated public int getId(); method public long getIdLong(); method public int read(@NonNull byte[], long, long); - method public void releaseSharedFilter(@NonNull String); method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter); method public int setMonitorEventMask(int); method public int start(); @@ -8681,7 +8681,11 @@ package android.os { } public final class NewUserRequest { + method @Nullable public String getAccountName(); + method @Nullable public android.os.PersistableBundle getAccountOptions(); + method @Nullable public String getAccountType(); method @Nullable public String getName(); + method @Nullable public android.graphics.Bitmap getUserIcon(); method @NonNull public String getUserType(); method public boolean isAdmin(); method public boolean isEphemeral(); @@ -8690,9 +8694,13 @@ package android.os { public static final class NewUserRequest.Builder { ctor public NewUserRequest.Builder(); method @NonNull public android.os.NewUserRequest build(); + method @NonNull public android.os.NewUserRequest.Builder setAccountName(@Nullable String); + method @NonNull public android.os.NewUserRequest.Builder setAccountOptions(@Nullable android.os.PersistableBundle); + method @NonNull public android.os.NewUserRequest.Builder setAccountType(@Nullable String); method @NonNull public android.os.NewUserRequest.Builder setAdmin(); method @NonNull public android.os.NewUserRequest.Builder setEphemeral(); method @NonNull public android.os.NewUserRequest.Builder setName(@Nullable String); + method @NonNull public android.os.NewUserRequest.Builder setUserIcon(@Nullable android.graphics.Bitmap); method @NonNull public android.os.NewUserRequest.Builder setUserType(@NonNull String); } @@ -8970,6 +8978,7 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String); field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED"; field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock"; field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background"; @@ -8981,6 +8990,7 @@ package android.os { field public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 4; // 0x4 field public static final int SWITCHABILITY_STATUS_USER_IN_CALL = 1; // 0x1 field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2 + field public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7; // 0x7 field public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST"; field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; @@ -13164,6 +13174,7 @@ package android.telephony.ims { method @NonNull public java.util.Set<android.telephony.ims.FeatureTagState> getDeregisteredFeatureTags(); method @NonNull public java.util.Set<android.telephony.ims.FeatureTagState> getDeregisteringFeatureTags(); method @NonNull public java.util.Set<java.lang.String> getRegisteredFeatureTags(); + method @NonNull public java.util.Set<java.lang.String> getRegisteringFeatureTags(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.DelegateRegistrationState> CREATOR; field public static final int DEREGISTERED_REASON_NOT_PROVISIONED = 1; // 0x1 @@ -13181,6 +13192,7 @@ package android.telephony.ims { method @NonNull public android.telephony.ims.DelegateRegistrationState.Builder addDeregisteringFeatureTag(@NonNull String, int); method @NonNull public android.telephony.ims.DelegateRegistrationState.Builder addRegisteredFeatureTag(@NonNull String); method @NonNull public android.telephony.ims.DelegateRegistrationState.Builder addRegisteredFeatureTags(@NonNull java.util.Set<java.lang.String>); + method @NonNull public android.telephony.ims.DelegateRegistrationState.Builder addRegisteringFeatureTags(@NonNull java.util.Set<java.lang.String>); method @NonNull public android.telephony.ims.DelegateRegistrationState build(); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f83b3a42d843..ba0b6aaf1bf2 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3250,12 +3250,6 @@ package android.window { field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR; } - public final class TaskFragmentAppearedInfo implements android.os.Parcelable { - method @NonNull public android.view.SurfaceControl getLeash(); - method @NonNull public android.window.TaskFragmentInfo getTaskFragmentInfo(); - field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentAppearedInfo> CREATOR; - } - public final class TaskFragmentCreationParams implements android.os.Parcelable { method @NonNull public android.os.IBinder getFragmentToken(); method @NonNull public android.graphics.Rect getInitialBounds(); @@ -3292,7 +3286,7 @@ package android.window { ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor); method @NonNull public java.util.concurrent.Executor getExecutor(); method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken(); - method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentAppearedInfo); + method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo); method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable); method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo); method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration); diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/core/java/android/accessibilityservice/MagnificationConfig.aidl index 3729c09168a6..fe415a864b80 100644 --- a/core/java/android/window/TaskFragmentAppearedInfo.aidl +++ b/core/java/android/accessibilityservice/MagnificationConfig.aidl @@ -14,10 +14,6 @@ * limitations under the License. */ -package android.window; +package android.accessibilityservice; -/** - * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer. - * @hide - */ -parcelable TaskFragmentAppearedInfo; +parcelable MagnificationConfig; diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java new file mode 100644 index 000000000000..8884508bb2b0 --- /dev/null +++ b/core/java/android/accessibilityservice/MagnificationConfig.java @@ -0,0 +1,250 @@ +/* + * 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 android.accessibilityservice; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class describes the magnification config for {@link AccessibilityService} to control the + * magnification. + * + * <p> + * When the magnification config uses {@link #DEFAULT_MODE}, + * {@link AccessibilityService} will be able to control the activated magnifier on the display. + * If there is no magnifier activated, it controls the last-activated magnification mode. + * If there is no magnifier activated before, it controls full-screen magnifier by default. + * </p> + * + * <p> + * When the magnification config uses {@link #FULLSCREEN_MODE}. {@link AccessibilityService} will + * be able to control full-screen magnifier on the display. + * </p> + * + * <p> + * When the magnification config uses {@link #WINDOW_MODE}. {@link AccessibilityService} will be + * able to control the activated window magnifier on the display. + * </p> + * + * <p> + * If the other magnification configs, scale centerX and centerY, are not set by the + * {@link Builder}, the configs should be current values or default values. And the center + * position ordinarily is the center of the screen. + * </p> + */ +public final class MagnificationConfig implements Parcelable { + + /** The controlling magnification mode. It controls the activated magnifier. */ + public static final int DEFAULT_MODE = 0; + /** The controlling magnification mode. It controls fullscreen magnifier. */ + public static final int FULLSCREEN_MODE = 1; + /** The controlling magnification mode. It controls window magnifier. */ + public static final int WINDOW_MODE = 2; + + @IntDef(prefix = {"MAGNIFICATION_MODE"}, value = { + DEFAULT_MODE, + FULLSCREEN_MODE, + WINDOW_MODE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface MAGNIFICATION_MODE { + } + + private int mMode = DEFAULT_MODE; + private float mScale = Float.NaN; + private float mCenterX = Float.NaN; + private float mCenterY = Float.NaN; + + private MagnificationConfig() { + /* do nothing */ + } + + private MagnificationConfig(@NonNull Parcel parcel) { + mMode = parcel.readInt(); + mScale = parcel.readFloat(); + mCenterX = parcel.readFloat(); + mCenterY = parcel.readFloat(); + } + + /** + * Returns the magnification mode that is the current activated mode or the controlling mode of + * the config. + * + * @return The magnification mode + */ + public int getMode() { + return mMode; + } + + /** + * Returns the magnification scale of the controlling magnifier + * + * @return the scale If the controlling magnifier is not activated, it returns 1 by default + */ + public float getScale() { + return mScale; + } + + /** + * Returns the screen-relative X coordinate of the center of the magnification viewport. + * + * @return the X coordinate. If the controlling magnifier is {@link #WINDOW_MODE} but not + * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link + * #FULLSCREEN_MODE} but not enabled, it returns 0 + */ + public float getCenterX() { + return mCenterX; + } + + /** + * Returns the screen-relative Y coordinate of the center of the magnification viewport. + * + * @return the Y coordinate If the controlling magnifier is {@link #WINDOW_MODE} but not + * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link + * #FULLSCREEN_MODE} but not enabled, it returns 0 + */ + public float getCenterY() { + return mCenterY; + } + + @NonNull + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder("MagnificationConfig["); + stringBuilder.append("mode: ").append(getMode()); + stringBuilder.append(", "); + stringBuilder.append("scale: ").append(getScale()); + stringBuilder.append(", "); + stringBuilder.append("centerX: ").append(getCenterX()); + stringBuilder.append(", "); + stringBuilder.append("centerY: ").append(getCenterY()); + stringBuilder.append("] "); + return stringBuilder.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mMode); + parcel.writeFloat(mScale); + parcel.writeFloat(mCenterX); + parcel.writeFloat(mCenterY); + } + + /** + * Builder for creating {@link MagnificationConfig} objects. + */ + public static final class Builder { + + private int mMode = DEFAULT_MODE; + private float mScale = Float.NaN; + private float mCenterX = Float.NaN; + private float mCenterY = Float.NaN; + + /** + * Creates a new Builder. + */ + public Builder() { + } + + /** + * Sets the magnification mode. + * + * @param mode The magnification mode + * @return This builder + */ + @NonNull + public MagnificationConfig.Builder setMode(@MAGNIFICATION_MODE int mode) { + mMode = mode; + return this; + } + + /** + * Sets the magnification scale. + * + * @param scale The magnification scale + * @return This builder + */ + @NonNull + public MagnificationConfig.Builder setScale(float scale) { + mScale = scale; + return this; + } + + /** + * Sets the X coordinate of the center of the magnification viewport. + * + * @param centerX the screen-relative X coordinate around which to + * center and scale, or {@link Float#NaN} to leave unchanged + * @return This builder + */ + @NonNull + public MagnificationConfig.Builder setCenterX(float centerX) { + mCenterX = centerX; + return this; + } + + /** + * Sets the Y coordinate of the center of the magnification viewport. + * + * @param centerY the screen-relative Y coordinate around which to + * center and scale, or {@link Float#NaN} to leave unchanged + * @return This builder + */ + @NonNull + public MagnificationConfig.Builder setCenterY(float centerY) { + mCenterY = centerY; + return this; + } + + /** + * Builds and returns a {@link MagnificationConfig} + */ + @NonNull + public MagnificationConfig build() { + MagnificationConfig magnificationConfig = new MagnificationConfig(); + magnificationConfig.mMode = mMode; + magnificationConfig.mScale = mScale; + magnificationConfig.mCenterX = mCenterX; + magnificationConfig.mCenterY = mCenterY; + return magnificationConfig; + } + } + + /** + * @see Parcelable.Creator + */ + public static final @NonNull Parcelable.Creator<MagnificationConfig> CREATOR = + new Parcelable.Creator<MagnificationConfig>() { + public MagnificationConfig createFromParcel(Parcel parcel) { + return new MagnificationConfig(parcel); + } + + public MagnificationConfig[] newArray(int size) { + return new MagnificationConfig[size]; + } + }; +} diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index f9cc3234a9ee..9f8d24662c8d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4082,6 +4082,34 @@ public class ActivityManager { } /** + * Gets the message that is shown when a user is switched from. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) + public @Nullable String getSwitchingFromUserMessage() { + try { + return getService().getSwitchingFromUserMessage(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Gets the message that is shown when a user is switched to. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) + public @Nullable String getSwitchingToUserMessage() { + try { + return getService().getSwitchingToUserMessage(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Uses the value defined by the platform. * * @hide diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 8bb40590d069..183e7141bfad 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -349,6 +349,8 @@ interface IActivityManager { void setPackageScreenCompatMode(in String packageName, int mode); @UnsupportedAppUsage boolean switchUser(int userid); + String getSwitchingFromUserMessage(); + String getSwitchingToUserMessage(); @UnsupportedAppUsage void setStopUserOnSwitch(int value); boolean removeTask(int taskId); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 311a60d63b8e..57b319634759 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -7867,7 +7867,7 @@ public class DevicePolicyManager { @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS }) public ComponentName getDeviceOwnerComponentOnAnyUser() { return getDeviceOwnerComponentInner(/* callingUserOnly =*/ false); @@ -7980,8 +7980,8 @@ public class DevicePolicyManager { * Called by the system to find out whether the device is managed by a Device Owner. * * @return whether the device is managed by a Device Owner. - * @throws SecurityException if the caller is not the device owner, does not hold the - * MANAGE_USERS permission and is not the system. + * @throws SecurityException if the caller is not the device owner, does not hold + * MANAGE_USERS or MANAGE_PROFILE_AND_DEVICE_OWNERS permissions and is not the system. * * @hide */ @@ -8002,7 +8002,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS + }) public String getDeviceOwnerNameOnAnyUser() { throwIfParentInstance("getDeviceOwnerNameOnAnyUser"); if (mService != null) { @@ -8392,7 +8395,10 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if the userId is invalid. */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS + }) public @Nullable String getProfileOwnerNameAsUser(int userId) throws IllegalArgumentException { throwIfParentInstance("getProfileOwnerNameAsUser"); if (mService != null) { @@ -11930,7 +11936,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS + }) @UserProvisioningState public int getUserProvisioningState() { throwIfParentInstance("getUserProvisioningState"); @@ -13439,7 +13448,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS + }) public boolean isManagedKiosk() { throwIfParentInstance("isManagedKiosk"); if (mService != null) { @@ -13468,7 +13480,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS + }) public boolean isUnattendedManagedKiosk() { throwIfParentInstance("isUnattendedManagedKiosk"); if (mService != null) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index a5a75a4684b5..25d1d53752a7 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6544,30 +6544,24 @@ public abstract class Context { @NonNull Configuration overrideConfiguration); /** - * Return a new Context object for the current Context but whose resources - * are adjusted to match the metrics of the given Display. Each call to this method - * returns a new instance of a Context object; Context objects are not - * shared, however common state (ClassLoader, other Resources for the - * same configuration) may be so the Context itself can be fairly lightweight. - * - * To obtain an instance of a {@link WindowManager} (see {@link #getSystemService(String)}) that - * is configured to show windows on the given display call - * {@link #createWindowContext(int, Bundle)} on the returned display Context or use an - * {@link android.app.Activity}. - * + * Returns a new <code>Context</code> object from the current context but with resources + * adjusted to match the metrics of <code>display</code>. Each call to this method + * returns a new instance of a context object. Context objects are not shared; however, + * common state (such as the {@link ClassLoader} and other resources for the same + * configuration) can be shared, so the <code>Context</code> itself is lightweight. * <p> - * Note that invoking #createDisplayContext(Display) from an UI context is not regarded - * as an UI context. In other words, it is not suggested to access UI components (such as - * obtain a {@link WindowManager} by {@link #getSystemService(String)}) - * from the context created from #createDisplayContext(Display). - * </p> - * - * @param display A {@link Display} object specifying the display for whose metrics the - * Context's resources should be tailored. + * To obtain an instance of {@link WindowManager} configured to show windows on the given + * display, call {@link #createWindowContext(int, Bundle)} on the returned display context, + * then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the + * returned window context. + * <p> + * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI + * context. Do not access UI components or obtain a {@link WindowManager} from the context + * created by <code>createDisplayContext(Display)</code>. * - * @return A {@link Context} for the display. + * @param display The display to which the current context's resources are adjusted. * - * @see #getSystemService(String) + * @return A context for the display. */ @DisplayContext public abstract Context createDisplayContext(@NonNull Display display); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 3c9b1f81da8a..2b8681ae56ea 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5522,8 +5522,8 @@ public class Intent implements Parcelable, Cloneable { /** * A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}, - * that specifies whether the system displayed attribution information in the - * permission usage system UI for the chosen entry. + * that specifies whether the permission usage system UI is showing attribution information + * for the chosen entry. * * <p> The extra can only be true if application has specified attributionsAreUserVisible * in its manifest. </p> @@ -6415,6 +6415,7 @@ public class Intent implements Parcelable, Cloneable { FLAG_RECEIVER_FROM_SHELL, FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS, FLAG_RECEIVER_OFFLOAD, + FLAG_RECEIVER_OFFLOAD_FOREGROUND, }) @Retention(RetentionPolicy.SOURCE) public @interface Flags {} @@ -6460,6 +6461,7 @@ public class Intent implements Parcelable, Cloneable { FLAG_RECEIVER_FROM_SHELL, FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS, FLAG_RECEIVER_OFFLOAD, + FLAG_RECEIVER_OFFLOAD_FOREGROUND, }) @Retention(RetentionPolicy.SOURCE) public @interface MutableFlags {} @@ -6914,6 +6916,14 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_RECEIVER_OFFLOAD = 0x80000000; /** + /** + * If set, when sending a broadcast the recipient will run on the system dedicated queue. + * + * @hide + */ + public static final int FLAG_RECEIVER_OFFLOAD_FOREGROUND = 0x00000800; + + /** * If this is an ordered broadcast, don't allow receivers to abort the broadcast. * They can still propagate results through to later receivers, but they can not prevent * later receivers from seeing the broadcast. diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 96a18dcac92a..3c8b6e9101b3 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1323,7 +1323,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Maximum flashlight brightness level.</p> * <p>If this value is greater than 1, then the device supports controlling the * flashlight brightness level via - * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}. + * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}. * If this value is equal to 1, flashlight brightness control is not supported. * The value for this key will be null for devices with no flash unit.</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> @@ -1335,7 +1335,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Default flashlight brightness level to be set via - * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.</p> + * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p> * <p>If flash unit is available this will be greater than or equal to 1 and less * or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p> * <p>Setting flashlight brightness above the default level diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index fd34fa4c9c7f..83e1061d8143 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -30,6 +30,7 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.window.DisplayWindowPolicyController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -379,6 +380,14 @@ public abstract class DisplayManagerInternal { public abstract void onEarlyInteractivityChange(boolean interactive); /** + * Get {@link DisplayWindowPolicyController} associated to the {@link DisplayInfo#displayId} + * + * @param displayId The id of the display. + * @return The associated {@link DisplayWindowPolicyController}. + */ + public abstract DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId); + + /** * Describes the requested power state of the display. * * This object is intended to describe the general characteristics of the diff --git a/core/java/android/net/nsd/INsdManager.aidl b/core/java/android/net/nsd/INsdManager.aidl index e9e8935a19b2..89e9cdbd4445 100644 --- a/core/java/android/net/nsd/INsdManager.aidl +++ b/core/java/android/net/nsd/INsdManager.aidl @@ -1,5 +1,5 @@ /** - * Copyright (c) 2012, The Android Open Source Project + * 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. @@ -16,16 +16,15 @@ package android.net.nsd; +import android.net.nsd.INsdManagerCallback; +import android.net.nsd.INsdServiceConnector; import android.os.Messenger; /** - * Interface that NsdService implements + * Interface that NsdService implements to connect NsdManager clients. * * {@hide} */ -interface INsdManager -{ - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - Messenger getMessenger(); - void setEnabled(boolean enable); +interface INsdManager { + INsdServiceConnector connect(INsdManagerCallback cb); } diff --git a/core/java/android/net/nsd/INsdManagerCallback.aidl b/core/java/android/net/nsd/INsdManagerCallback.aidl new file mode 100644 index 000000000000..1a262ec0e9dd --- /dev/null +++ b/core/java/android/net/nsd/INsdManagerCallback.aidl @@ -0,0 +1,39 @@ +/** + * 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 android.net.nsd; + +import android.os.Messenger; +import android.net.nsd.NsdServiceInfo; + +/** + * Callbacks from NsdService to NsdManager + * @hide + */ +oneway interface INsdManagerCallback { + void onDiscoverServicesStarted(int listenerKey, in NsdServiceInfo info); + void onDiscoverServicesFailed(int listenerKey, int error); + void onServiceFound(int listenerKey, in NsdServiceInfo info); + void onServiceLost(int listenerKey, in NsdServiceInfo info); + void onStopDiscoveryFailed(int listenerKey, int error); + void onStopDiscoverySucceeded(int listenerKey); + void onRegisterServiceFailed(int listenerKey, int error); + void onRegisterServiceSucceeded(int listenerKey, in NsdServiceInfo info); + void onUnregisterServiceFailed(int listenerKey, int error); + void onUnregisterServiceSucceeded(int listenerKey); + void onResolveServiceFailed(int listenerKey, int error); + void onResolveServiceSucceeded(int listenerKey, in NsdServiceInfo info); +} diff --git a/core/java/android/net/nsd/INsdServiceConnector.aidl b/core/java/android/net/nsd/INsdServiceConnector.aidl new file mode 100644 index 000000000000..b06ae55b150e --- /dev/null +++ b/core/java/android/net/nsd/INsdServiceConnector.aidl @@ -0,0 +1,35 @@ +/** + * 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 android.net.nsd; + +import android.net.nsd.INsdManagerCallback; +import android.net.nsd.NsdServiceInfo; +import android.os.Messenger; + +/** + * Interface that NsdService implements for each NsdManager client. + * + * {@hide} + */ +interface INsdServiceConnector { + void registerService(int listenerKey, in NsdServiceInfo serviceInfo); + void unregisterService(int listenerKey); + void discoverServices(int listenerKey, in NsdServiceInfo serviceInfo); + void stopDiscovery(int listenerKey); + void resolveService(int listenerKey, in NsdServiceInfo serviceInfo); + void startDaemon(); +}
\ No newline at end of file diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java index ae8d0101947d..6c597e26e042 100644 --- a/core/java/android/net/nsd/NsdManager.java +++ b/core/java/android/net/nsd/NsdManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * 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. @@ -31,17 +31,13 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; -import java.util.concurrent.CountDownLatch; - /** * The Network Service Discovery Manager class provides the API to discover services * on a network. As an example, if device A and device B are connected over a Wi-Fi @@ -234,6 +230,11 @@ public final class NsdManager { /** @hide */ public static final int NATIVE_DAEMON_EVENT = BASE + 26; + /** @hide */ + public static final int REGISTER_CLIENT = BASE + 27; + /** @hide */ + public static final int UNREGISTER_CLIENT = BASE + 28; + /** Dns based service discovery protocol */ public static final int PROTOCOL_DNS_SD = 0x0001; @@ -274,7 +275,7 @@ public final class NsdManager { private static final int FIRST_LISTENER_KEY = 1; - private final INsdManager mService; + private final INsdServiceConnector mService; private final Context mContext; private int mListenerKey = FIRST_LISTENER_KEY; @@ -282,9 +283,7 @@ public final class NsdManager { private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>(); private final Object mMapLock = new Object(); - private final AsyncChannel mAsyncChannel = new AsyncChannel(); - private ServiceHandler mHandler; - private final CountDownLatch mConnected = new CountDownLatch(1); + private final ServiceHandler mHandler; /** * Create a new Nsd instance. Applications use @@ -295,18 +294,108 @@ public final class NsdManager { * is a system private class. */ public NsdManager(Context context, INsdManager service) { - mService = service; mContext = context; - init(); + + HandlerThread t = new HandlerThread("NsdManager"); + t.start(); + mHandler = new ServiceHandler(t.getLooper()); + + try { + mService = service.connect(new NsdCallbackImpl(mHandler)); + } catch (RemoteException e) { + throw new RuntimeException("Failed to connect to NsdService"); + } + + // Only proactively start the daemon if the target SDK < S, otherwise the internal service + // would automatically start/stop the native daemon as needed. + if (!CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) { + try { + mService.startDaemon(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to proactively start daemon"); + // Continue: the daemon can still be started on-demand later + } + } } - /** - * @hide - */ - @VisibleForTesting - public void disconnect() { - mAsyncChannel.disconnect(); - mHandler.getLooper().quitSafely(); + private static class NsdCallbackImpl extends INsdManagerCallback.Stub { + private final Handler mServHandler; + + NsdCallbackImpl(Handler serviceHandler) { + mServHandler = serviceHandler; + } + + private void sendInfo(int message, int listenerKey, NsdServiceInfo info) { + mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey, info)); + } + + private void sendError(int message, int listenerKey, int error) { + mServHandler.sendMessage(mServHandler.obtainMessage(message, error, listenerKey)); + } + + private void sendNoArg(int message, int listenerKey) { + mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey)); + } + + @Override + public void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) { + sendInfo(DISCOVER_SERVICES_STARTED, listenerKey, info); + } + + @Override + public void onDiscoverServicesFailed(int listenerKey, int error) { + sendError(DISCOVER_SERVICES_FAILED, listenerKey, error); + } + + @Override + public void onServiceFound(int listenerKey, NsdServiceInfo info) { + sendInfo(SERVICE_FOUND, listenerKey, info); + } + + @Override + public void onServiceLost(int listenerKey, NsdServiceInfo info) { + sendInfo(SERVICE_LOST, listenerKey, info); + } + + @Override + public void onStopDiscoveryFailed(int listenerKey, int error) { + sendError(STOP_DISCOVERY_FAILED, listenerKey, error); + } + + @Override + public void onStopDiscoverySucceeded(int listenerKey) { + sendNoArg(STOP_DISCOVERY_SUCCEEDED, listenerKey); + } + + @Override + public void onRegisterServiceFailed(int listenerKey, int error) { + sendError(REGISTER_SERVICE_FAILED, listenerKey, error); + } + + @Override + public void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) { + sendInfo(REGISTER_SERVICE_SUCCEEDED, listenerKey, info); + } + + @Override + public void onUnregisterServiceFailed(int listenerKey, int error) { + sendError(UNREGISTER_SERVICE_FAILED, listenerKey, error); + } + + @Override + public void onUnregisterServiceSucceeded(int listenerKey) { + sendNoArg(UNREGISTER_SERVICE_SUCCEEDED, listenerKey); + } + + @Override + public void onResolveServiceFailed(int listenerKey, int error) { + sendError(RESOLVE_SERVICE_FAILED, listenerKey, error); + } + + @Override + public void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) { + sendInfo(RESOLVE_SERVICE_SUCCEEDED, listenerKey, info); + } } /** @@ -376,19 +465,6 @@ public final class NsdManager { public void handleMessage(Message message) { final int what = message.what; final int key = message.arg2; - switch (what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); - return; - case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: - mConnected.countDown(); - return; - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: - Log.e(TAG, "Channel lost"); - return; - default: - break; - } final Object listener; final NsdServiceInfo ns; synchronized (mMapLock) { @@ -504,36 +580,6 @@ public final class NsdManager { } /** - * Initialize AsyncChannel - */ - private void init() { - final Messenger messenger = getMessenger(); - if (messenger == null) { - fatal("Failed to obtain service Messenger"); - } - HandlerThread t = new HandlerThread("NsdManager"); - t.start(); - mHandler = new ServiceHandler(t.getLooper()); - mAsyncChannel.connect(mContext, mHandler, messenger); - try { - mConnected.await(); - } catch (InterruptedException e) { - fatal("Interrupted wait at init"); - } - if (CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) { - return; - } - // Only proactively start the daemon if the target SDK < S, otherwise the internal service - // would automatically start/stop the native daemon as needed. - mAsyncChannel.sendMessage(DAEMON_STARTUP); - } - - private static void fatal(String msg) { - Log.e(TAG, msg); - throw new RuntimeException(msg); - } - - /** * Register a service to be discovered by other services. * * <p> The function call immediately returns after sending a request to register service @@ -556,7 +602,11 @@ public final class NsdManager { checkServiceInfo(serviceInfo); checkProtocol(protocolType); int key = putListener(listener, serviceInfo); - mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo); + try { + mService.registerService(key, serviceInfo); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @@ -574,7 +624,11 @@ public final class NsdManager { */ public void unregisterService(RegistrationListener listener) { int id = getListenerKey(listener); - mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id); + try { + mService.unregisterService(id); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @@ -613,7 +667,11 @@ public final class NsdManager { s.setServiceType(serviceType); int key = putListener(listener, s); - mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, key, s); + try { + mService.discoverServices(key, s); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @@ -634,7 +692,11 @@ public final class NsdManager { */ public void stopServiceDiscovery(DiscoveryListener listener) { int id = getListenerKey(listener); - mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id); + try { + mService.stopDiscovery(id); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @@ -649,29 +711,10 @@ public final class NsdManager { public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) { checkServiceInfo(serviceInfo); int key = putListener(listener, serviceInfo); - mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, key, serviceInfo); - } - - /** Internal use only @hide */ - public void setEnabled(boolean enabled) { - try { - mService.setEnabled(enabled); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Get a reference to NsdService handler. This is used to establish - * an AsyncChannel communication with the service - * - * @return Messenger pointing to the NsdService handler - */ - private Messenger getMessenger() { try { - return mService.getMessenger(); + mService.resolveService(key, serviceInfo); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + e.rethrowFromSystemServer(); } } diff --git a/core/java/android/net/nsd/NsdServiceInfo.aidl b/core/java/android/net/nsd/NsdServiceInfo.aidl new file mode 100644 index 000000000000..657bdd1e8706 --- /dev/null +++ b/core/java/android/net/nsd/NsdServiceInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.net.nsd; + +@JavaOnlyStableParcelable parcelable NsdServiceInfo;
\ No newline at end of file diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index 0af322e885b1..09540132fe0d 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -528,6 +528,7 @@ public final class ApduServiceInfo implements Parcelable { public String toString() { StringBuilder out = new StringBuilder("ApduService: "); out.append(getComponent()); + out.append(", UID: " + mUid); out.append(", description: " + mDescription); out.append(", Static AID Groups: "); for (AidGroup aidGroup : mStaticAidGroups.values()) { @@ -546,7 +547,8 @@ public final class ApduServiceInfo implements Parcelable { if (!(o instanceof ApduServiceInfo)) return false; ApduServiceInfo thatService = (ApduServiceInfo) o; - return thatService.getComponent().equals(this.getComponent()); + return thatService.getComponent().equals(this.getComponent()) + && thatService.getUid() == this.getUid(); } @Override @@ -619,8 +621,9 @@ public final class ApduServiceInfo implements Parcelable { }; public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(" " + getComponent() + - " (Description: " + getDescription() + ")"); + pw.println(" " + getComponent() + + " (Description: " + getDescription() + ")" + + " (UID: " + getUid() + ")"); if (mOnHost) { pw.println(" On Host Service"); } else { diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index d498535ce52c..0a9fe90f2524 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -30,6 +30,7 @@ import android.content.pm.PackageManager; import android.nfc.INfcCardEmulation; import android.nfc.NfcAdapter; import android.os.RemoteException; +import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; @@ -83,6 +84,13 @@ public final class CardEmulation { public static final String EXTRA_SERVICE_COMPONENT = "component"; /** + * The caller userId extra for {@link #ACTION_CHANGE_DEFAULT}. + * + * @see #ACTION_CHANGE_DEFAULT + */ + public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID"; + + /** * Category used for NFC payment services. */ public static final String CATEGORY_PAYMENT = "payment"; @@ -269,8 +277,8 @@ public final class CardEmulation { if (CATEGORY_PAYMENT.equals(category)) { boolean preferForeground = false; try { - preferForeground = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0; + preferForeground = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.NFC_PAYMENT_FOREGROUND, UserHandle.myUserId()) != 0; } catch (SettingNotFoundException e) { } return preferForeground; @@ -829,6 +837,28 @@ public final class CardEmulation { /** * @hide */ + public boolean setDefaultForNextTap(int userId, ComponentName service) { + try { + return sService.setDefaultForNextTap(userId, service); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.setDefaultForNextTap(userId, service); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * @hide + */ public List<ApduServiceInfo> getServices(String category) { try { return sService.getServices(mContext.getUserId(), category); @@ -849,6 +879,28 @@ public final class CardEmulation { } /** + * @hide + */ + public List<ApduServiceInfo> getServices(String category, int userId) { + try { + return sService.getServices(userId, category); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return null; + } + try { + return sService.getServices(userId, category); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return null; + } + } + } + + /** * A valid AID according to ISO/IEC 7816-4: * <ul> * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars) diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java index 80e8579dd73f..557e41a2b103 100644 --- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java +++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java @@ -25,7 +25,6 @@ import android.content.pm.PackageManager; import android.nfc.INfcFCardEmulation; import android.nfc.NfcAdapter; import android.os.RemoteException; -import android.os.UserHandle; import android.util.Log; import java.util.HashMap; diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java index c2b33dd51b0c..f8f7dfe034b5 100644 --- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java +++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java @@ -237,6 +237,7 @@ public final class NfcFServiceInfo implements Parcelable { public String toString() { StringBuilder out = new StringBuilder("NfcFService: "); out.append(getComponent()); + out.append(", UID: " + mUid); out.append(", description: " + mDescription); out.append(", System Code: " + mSystemCode); if (mDynamicSystemCode != null) { @@ -257,6 +258,7 @@ public final class NfcFServiceInfo implements Parcelable { NfcFServiceInfo thatService = (NfcFServiceInfo) o; if (!thatService.getComponent().equals(this.getComponent())) return false; + if (thatService.getUid() != this.getUid()) return false; if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false; if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false; if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false; @@ -321,8 +323,9 @@ public final class NfcFServiceInfo implements Parcelable { }; public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(" " + getComponent() + - " (Description: " + getDescription() + ")"); + pw.println(" " + getComponent() + + " (Description: " + getDescription() + ")" + + " (UID: " + getUid() + ")"); pw.println(" System Code: " + getSystemCode()); pw.println(" NFCID2: " + getNfcid2()); pw.println(" T3tPmm: " + getT3tPmm()); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index b83970625ee4..50ca9ff3576f 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -20,6 +20,7 @@ package android.os; import android.os.Bundle; import android.os.IUserRestrictionsListener; import android.os.PersistableBundle; +import android.os.UserHandle; import android.os.UserManager; import android.content.pm.UserInfo; import android.content.IntentSender; @@ -91,6 +92,9 @@ interface IUserManager { boolean markGuestForDeletion(int userId); UserInfo findCurrentGuestUser(); boolean isQuietModeEnabled(int userId); + UserHandle createUserWithAttributes(in String userName, in String userType, int flags, + in Bitmap userIcon, + in String accountName, in String accountType, in PersistableBundle accountOptions); void setSeedAccountData(int userId, in String accountName, in String accountType, in PersistableBundle accountOptions, boolean persist); String getSeedAccountName(int userId); @@ -98,6 +102,7 @@ interface IUserManager { PersistableBundle getSeedAccountOptions(int userId); void clearSeedAccountData(int userId); boolean someUserHasSeedAccount(in String accountName, in String accountType); + boolean someUserHasAccount(in String accountName, in String accountType); boolean isProfile(int userId); boolean isManagedProfile(int userId); boolean isCloneProfile(int userId); diff --git a/core/java/android/os/NewUserRequest.java b/core/java/android/os/NewUserRequest.java index 2ebc01f2d3d0..b0e1f91c47c3 100644 --- a/core/java/android/os/NewUserRequest.java +++ b/core/java/android/os/NewUserRequest.java @@ -17,7 +17,11 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.content.pm.UserInfo; +import android.graphics.Bitmap; +import android.text.TextUtils; /** * Contains necessary information to create user using @@ -26,6 +30,7 @@ import android.annotation.SystemApi; * @hide */ @SystemApi +@SuppressLint("PackageLayering") public final class NewUserRequest { @Nullable private final String mName; @@ -33,16 +38,24 @@ public final class NewUserRequest { private final boolean mEphemeral; @NonNull private final String mUserType; + private final Bitmap mUserIcon; + private final String mAccountName; + private final String mAccountType; + private final PersistableBundle mAccountOptions; private NewUserRequest(Builder builder) { mName = builder.mName; mAdmin = builder.mAdmin; mEphemeral = builder.mEphemeral; mUserType = builder.mUserType; + mUserIcon = builder.mUserIcon; + mAccountName = builder.mAccountName; + mAccountType = builder.mAccountType; + mAccountOptions = builder.mAccountOptions; } /** - * Gets the user name. + * Returns the name of the user. */ @Nullable public String getName() { @@ -50,7 +63,7 @@ public final class NewUserRequest { } /** - * Is user Ephemenral? + * Returns whether the user is ephemeral. * * <p> Ephemeral user will be removed after leaving the foreground. */ @@ -59,7 +72,7 @@ public final class NewUserRequest { } /** - * Is user Admin? + * Returns whether the user is an admin. * * <p> Admin user is with administrative privileges and such user can create and * delete users. @@ -69,7 +82,17 @@ public final class NewUserRequest { } /** - * Gets user type. + * Returns the calculated flags for user creation. + */ + int getFlags() { + int flags = 0; + if (isAdmin()) flags |= UserInfo.FLAG_ADMIN; + if (isEphemeral()) flags |= UserInfo.FLAG_EPHEMERAL; + return flags; + } + + /** + * Returns the user type. * * <p> Supported types are {@link UserManager.USER_TYPE_FULL_SECONDARY} and * {@link USER_TYPE_FULL_GUEST} @@ -79,25 +102,71 @@ public final class NewUserRequest { return mUserType; } + /** + * Returns the user icon. + */ + @Nullable + public Bitmap getUserIcon() { + return mUserIcon; + } + + /** + * Returns the account name. + */ + @Nullable + public String getAccountName() { + return mAccountName; + } + + /** + * Returns the account type. + */ + @Nullable + public String getAccountType() { + return mAccountType; + } + + /** + * Returns the account options. + */ + @SuppressLint("NullableCollection") + @Nullable + public PersistableBundle getAccountOptions() { + return mAccountOptions; + } + @Override public String toString() { - return String.format( - "NewUserRequest- UserName:%s, userType:%s, IsAdmin:%s, IsEphemeral:%s.", mName, - mUserType, mAdmin, mEphemeral); + return "NewUserRequest{" + + "mName='" + mName + '\'' + + ", mAdmin=" + mAdmin + + ", mEphemeral=" + mEphemeral + + ", mUserType='" + mUserType + '\'' + + ", mAccountName='" + mAccountName + '\'' + + ", mAccountType='" + mAccountType + '\'' + + ", mAccountOptions=" + mAccountOptions + + '}'; } /** * Builder for building {@link NewUserRequest} */ + @SuppressLint("PackageLayering") public static final class Builder { private String mName; private boolean mAdmin; private boolean mEphemeral; private String mUserType = UserManager.USER_TYPE_FULL_SECONDARY; + private Bitmap mUserIcon; + private String mAccountName; + private String mAccountType; + private PersistableBundle mAccountOptions; /** * Sets user name. + * + * @return This object for method chaining. */ @NonNull public Builder setName(@Nullable String name) { @@ -110,6 +179,8 @@ public final class NewUserRequest { * * <p> Admin user is with administrative privileges and such user can create * and delete users. + * + * @return This object for method chaining. */ @NonNull public Builder setAdmin() { @@ -121,6 +192,8 @@ public final class NewUserRequest { * Sets user as ephemeral. * * <p> Ephemeral user will be removed after leaving the foreground. + * + * @return This object for method chaining. */ @NonNull public Builder setEphemeral() { @@ -134,6 +207,8 @@ public final class NewUserRequest { * Supported types are {@link UserManager.USER_TYPE_FULL_SECONDARY} and * {@link UserManager.USER_TYPE_FULL_GUEST}. Default value is * {@link UserManager.USER_TYPE_FULL_SECONDARY}. + * + * @return This object for method chaining. */ @NonNull public Builder setUserType(@NonNull String type) { @@ -142,6 +217,54 @@ public final class NewUserRequest { } /** + * Sets user icon. + * + * @return This object for method chaining. + */ + @NonNull + public Builder setUserIcon(@Nullable Bitmap userIcon) { + mUserIcon = userIcon; + return this; + } + + /** + * Sets account name that will be used by the setup wizard to initialize the user. + * + * @see android.accounts.Account + * @return This object for method chaining. + */ + @NonNull + public Builder setAccountName(@Nullable String accountName) { + mAccountName = accountName; + return this; + } + + /** + * Sets account type for the account to be created. This is required if the account name + * is not null. This will be used by the setup wizard to initialize the user. + * + * @see android.accounts.Account + * @return This object for method chaining. + */ + @NonNull + public Builder setAccountType(@Nullable String accountType) { + mAccountType = accountType; + return this; + } + + /** + * Sets account options that can contain account-specific extra information + * to be used by setup wizard to initialize the account for the user. + * + * @return This object for method chaining. + */ + @NonNull + public Builder setAccountOptions(@Nullable PersistableBundle accountOptions) { + mAccountOptions = accountOptions; + return this; + } + + /** * Builds {@link NewUserRequest} * * @throws IllegalStateException if builder is configured with incompatible properties and @@ -165,6 +288,11 @@ public final class NewUserRequest { && mUserType != UserManager.USER_TYPE_FULL_GUEST) { throw new IllegalStateException("Unsupported user type: " + mUserType); } + + if (TextUtils.isEmpty(mAccountName) != TextUtils.isEmpty(mAccountType)) { + throw new IllegalStateException( + "Account name and account type should be provided together."); + } } } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 94375c0e949f..cf4ce9b43cf2 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -70,6 +70,7 @@ 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.Set; /** @@ -1661,6 +1662,14 @@ public class UserManager { public static final int USER_OPERATION_ERROR_MAX_USERS = 6; /** + * Indicates user operation failed because a user with that account already exists. + * + * @hide + */ + @SystemApi + public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7; + + /** * Result returned from various user operations. * * @hide @@ -1673,7 +1682,8 @@ public class UserManager { USER_OPERATION_ERROR_MAX_RUNNING_USERS, USER_OPERATION_ERROR_CURRENT_USER, USER_OPERATION_ERROR_LOW_STORAGE, - USER_OPERATION_ERROR_MAX_USERS + USER_OPERATION_ERROR_MAX_USERS, + USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS }) public @interface UserOperationResult {} @@ -3159,26 +3169,24 @@ public class UserManager { @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) public @NonNull NewUserResponse createUser(@NonNull NewUserRequest newUserRequest) { - UserInfo user = null; - int operationResult = USER_OPERATION_ERROR_UNKNOWN; try { - user = createUser(newUserRequest.getName(), newUserRequest.getUserType(), - determineFlagsForUserCreation(newUserRequest)); - } catch (UserOperationException e) { + final UserHandle userHandle = mService.createUserWithAttributes( + newUserRequest.getName(), + newUserRequest.getUserType(), + newUserRequest.getFlags(), + newUserRequest.getUserIcon(), + newUserRequest.getAccountName(), + newUserRequest.getAccountType(), + newUserRequest.getAccountOptions()); + + return new NewUserResponse(userHandle, USER_OPERATION_SUCCESS); + + } catch (ServiceSpecificException e) { Log.w(TAG, "Exception while creating user " + newUserRequest, e); - operationResult = e.getUserOperationResult(); - } - if (user == null) { - return new NewUserResponse(null, operationResult); + return new NewUserResponse(null, e.errorCode); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } - return new NewUserResponse(user.getUserHandle(), USER_OPERATION_SUCCESS); - } - - private int determineFlagsForUserCreation(NewUserRequest newUserRequest) { - int flags = 0; - if (newUserRequest.isAdmin()) flags |= UserInfo.FLAG_ADMIN; - if (newUserRequest.isEphemeral()) flags |= UserInfo.FLAG_EPHEMERAL; - return flags; } /** @@ -4913,12 +4921,12 @@ public class UserManager { } /** - * @hide * Checks if any uninitialized user has the specific seed account name and type. * * @param accountName The account name to check for * @param accountType The account type of the account to check for * @return whether the seed account was found + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean someUserHasSeedAccount(String accountName, String accountType) { @@ -4930,6 +4938,29 @@ public class UserManager { } /** + * Checks if any initialized or uninitialized user has the specific account name and type. + * + * @param accountName The account name to check for + * @param accountType The account type of the account to check for + * @return whether the account was found + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + public boolean someUserHasAccount( + @NonNull String accountName, @NonNull String accountType) { + Objects.requireNonNull(accountName, "accountName must not be null"); + Objects.requireNonNull(accountType, "accountType must not be null"); + + try { + return mService.someUserHasAccount(accountName, accountType); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * @hide * User that enforces a restriction. * diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ae09b45a8f99..cc95c1f6c60b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4537,6 +4537,25 @@ public final class Settings { "haptic_feedback_intensity"; /** + * The intensity of haptic feedback vibrations for interaction with hardware components from + * the device, like buttons and sensors, if configurable. + * + * Not all devices are capable of changing their feedback intensity; on these devices + * there will likely be no difference between the various vibration intensities except for + * intensity 0 (off) and the rest. + * + * <b>Values:</b><br/> + * 0 - Vibration is disabled<br/> + * 1 - Weak vibrations<br/> + * 2 - Medium vibrations<br/> + * 3 - Strong vibrations + * @hide + */ + @Readable + public static final String HARDWARE_HAPTIC_FEEDBACK_INTENSITY = + "hardware_haptic_feedback_intensity"; + + /** * Ringer volume. This is used internally, changing this value will not * change the volume. See AudioManager. * diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 1b38f590edd6..5d84af051382 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -3584,6 +3584,23 @@ public final class Telephony { "content://telephony/carriers/enforce_managed"); /** + * The {@code content://} style URL for the perferred APN used for internet. + * + * @hide + */ + public static final Uri PREFERRED_APN_URI = Uri.parse( + "content://telephony/carriers/preferapn/subId/"); + + /** + * The {@code content://} style URL for the perferred APN set id. + * + * @hide + */ + public static final Uri PREFERRED_APN_SET_URI = Uri.parse( + "content://telephony/carriers/preferapnset/subId/"); + + + /** * The column name for ENFORCE_MANAGED_URI, indicates whether DPC-owned APNs are enforced. * @hide */ diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index ec613edeedb4..c5bc99d042d7 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -153,11 +153,20 @@ public class HapticFeedbackConstants { /** * Invocation of the voice assistant via hardware button. + * This is a private constant. Feel free to renumber as desired. * @hide */ public static final int ASSISTANT_BUTTON = 10002; /** + * The user has performed a long press on the power button hardware that is resulting + * in an action being performed. + * This is a private constant. Feel free to renumber as desired. + * @hide + */ + public static final int LONG_PRESS_POWER_BUTTON = 10003; + + /** * Flag for {@link View#performHapticFeedback(int, int) * View.performHapticFeedback(int, int)}: Ignore the setting in the * view for whether to perform haptic feedback, do it always. diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 1460cb251c72..c3a638c4c36a 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -295,7 +295,8 @@ public class KeyEvent extends InputEvent implements Parcelable { /** Key code constant: Fast Forward media key. */ public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; /** Key code constant: Mute key. - * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */ + * Mute key for the microphone (unlike {@link #KEYCODE_VOLUME_MUTE}, which is the speaker mute + * key). */ public static final int KEYCODE_MUTE = 91; /** Key code constant: Page Up key. */ public static final int KEYCODE_PAGE_UP = 92; @@ -482,9 +483,10 @@ public class KeyEvent extends InputEvent implements Parcelable { /** Key code constant: Numeric keypad ')' key. */ public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163; /** Key code constant: Volume Mute key. - * Mutes the speaker, unlike {@link #KEYCODE_MUTE}. - * This key should normally be implemented as a toggle such that the first press - * mutes the speaker and the second press restores the original volume. */ + * Mute key for speaker (unlike {@link #KEYCODE_MUTE}, which is the mute key for the + * microphone). This key should normally be implemented as a toggle such that the first press + * mutes the speaker and the second press restores the original volume. + */ public static final int KEYCODE_VOLUME_MUTE = 164; /** Key code constant: Info key. * Common on TV remotes to show additional information related to what is diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 3b4fcc0adccb..7fe810acd6f3 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -129,17 +129,15 @@ import java.util.function.Consumer; /** * The interface that apps use to talk to the window manager. - * </p><p> - * Each window manager instance is bound to a particular {@link Display}. - * To obtain a {@link WindowManager} for a different display, use - * {@link Context#createDisplayContext} to obtain a {@link Context} for that - * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> - * to get the WindowManager. - * </p><p> - * The simplest way to show a window on another display is to create a - * {@link Presentation}. The presentation will automatically obtain a - * {@link WindowManager} and {@link Context} for that display. - * </p> + * <p> + * Each window manager instance is bound to a {@link Display}. To obtain the + * <code>WindowManager</code> associated with a display, + * call {@link Context#createWindowContext(Display, int, Bundle)} to get the display's UI context, + * then call {@link Context#getSystemService(String)} or {@link Context#getSystemService(Class)} on + * the UI context. + * <p> + * The simplest way to show a window on a particular display is to create a {@link Presentation}, + * which automatically obtains a <code>WindowManager</code> and context for the display. */ @SystemService(Context.WINDOW_SERVICE) public interface WindowManager extends ViewManager { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 4730eaa8eee9..7680aa6c61fd 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -3957,8 +3957,10 @@ public class AccessibilityNodeInfo implements Parcelable { } if (isBitSet(nonDefaultFields, fieldIndex++)) { + parcel.writeString(mCollectionItemInfo.getRowTitle()); parcel.writeInt(mCollectionItemInfo.getRowIndex()); parcel.writeInt(mCollectionItemInfo.getRowSpan()); + parcel.writeString(mCollectionItemInfo.getColumnTitle()); parcel.writeInt(mCollectionItemInfo.getColumnIndex()); parcel.writeInt(mCollectionItemInfo.getColumnSpan()); parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0); @@ -4100,8 +4102,9 @@ public class AccessibilityNodeInfo implements Parcelable { ci.mHierarchical, ci.mSelectionMode); CollectionItemInfo cii = other.mCollectionItemInfo; mCollectionItemInfo = (cii == null) ? null - : new CollectionItemInfo(cii.mRowIndex, cii.mRowSpan, cii.mColumnIndex, - cii.mColumnSpan, cii.mHeading, cii.mSelected); + : new CollectionItemInfo(cii.mRowTitle, cii.mRowIndex, cii.mRowSpan, + cii.mColumnTitle, cii.mColumnIndex, cii.mColumnSpan, + cii.mHeading, cii.mSelected); ExtraRenderingInfo ti = other.mExtraRenderingInfo; mExtraRenderingInfo = (ti == null) ? null : new ExtraRenderingInfo(ti); @@ -4221,8 +4224,10 @@ public class AccessibilityNodeInfo implements Parcelable { if (mCollectionItemInfo != null) mCollectionItemInfo.recycle(); mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++) ? CollectionItemInfo.obtain( + parcel.readString(), parcel.readInt(), parcel.readInt(), + parcel.readString(), parcel.readInt(), parcel.readInt(), parcel.readInt() == 1, @@ -5570,8 +5575,9 @@ public class AccessibilityNodeInfo implements Parcelable { * @hide */ public static CollectionItemInfo obtain(CollectionItemInfo other) { - return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex, - other.mColumnSpan, other.mHeading, other.mSelected); + return CollectionItemInfo.obtain(other.mRowTitle, other.mRowIndex, other.mRowSpan, + other.mColumnTitle, other.mColumnIndex, other.mColumnSpan, other.mHeading, + other.mSelected); } /** @@ -5612,10 +5618,36 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static CollectionItemInfo obtain(int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading, boolean selected) { + return obtain(null, rowIndex, rowSpan, null, columnIndex, + columnSpan, heading, selected); + } + + /** + * Obtains a pooled instance. + * + * <p>In most situations object pooling is not beneficial. Creates a new instance using the + * constructor {@link + * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int, + * int, int, int, boolean, boolean)} instead. + * + * @param rowTitle The row title at which the item is located. + * @param rowIndex The row index at which the item is located. + * @param rowSpan The number of rows the item spans. + * @param columnTitle The column title at which the item is located. + * @param columnIndex The column index at which the item is located. + * @param columnSpan The number of columns the item spans. + * @param heading Whether the item is a heading. (Prefer + * {@link AccessibilityNodeInfo#setHeading(boolean)}) + * @param selected Whether the item is selected. + */ + @NonNull + public static CollectionItemInfo obtain(@Nullable String rowTitle, int rowIndex, + int rowSpan, @Nullable String columnTitle, int columnIndex, int columnSpan, + boolean heading, boolean selected) { final CollectionItemInfo info = sPool.acquire(); if (info == null) { - return new CollectionItemInfo( - rowIndex, rowSpan, columnIndex, columnSpan, heading, selected); + return new CollectionItemInfo(rowTitle, rowIndex, rowSpan, columnTitle, + columnIndex, columnSpan, heading, selected); } info.mRowIndex = rowIndex; @@ -5624,6 +5656,8 @@ public class AccessibilityNodeInfo implements Parcelable { info.mColumnSpan = columnSpan; info.mHeading = heading; info.mSelected = selected; + info.mRowTitle = rowTitle; + info.mColumnTitle = columnTitle; return info; } @@ -5633,6 +5667,8 @@ public class AccessibilityNodeInfo implements Parcelable { private int mColumnSpan; private int mRowSpan; private boolean mSelected; + private String mRowTitle; + private String mColumnTitle; /** * Creates a new instance. @@ -5660,12 +5696,33 @@ public class AccessibilityNodeInfo implements Parcelable { */ public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading, boolean selected) { + this(null, rowIndex, rowSpan, null, columnIndex, columnSpan, + heading, selected); + } + + /** + * Creates a new instance. + * + * @param rowTitle The row title at which the item is located. + * @param rowIndex The row index at which the item is located. + * @param rowSpan The number of rows the item spans. + * @param columnTitle The column title at which the item is located. + * @param columnIndex The column index at which the item is located. + * @param columnSpan The number of columns the item spans. + * @param heading Whether the item is a heading. + * @param selected Whether the item is selected. + */ + public CollectionItemInfo(@Nullable String rowTitle, int rowIndex, int rowSpan, + @Nullable String columnTitle, int columnIndex, int columnSpan, boolean heading, + boolean selected) { mRowIndex = rowIndex; mRowSpan = rowSpan; mColumnIndex = columnIndex; mColumnSpan = columnSpan; mHeading = heading; mSelected = selected; + mRowTitle = rowTitle; + mColumnTitle = columnTitle; } /** @@ -5725,6 +5782,26 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the row title at which the item is located. + * + * @return The row title. + */ + @Nullable + public String getRowTitle() { + return mRowTitle; + } + + /** + * Gets the column title at which the item is located. + * + * @return The column title. + */ + @Nullable + public String getColumnTitle() { + return mColumnTitle; + } + + /** * Recycles this instance. * * <p>In most situations object pooling is not beneficial, and recycling is not necessary. @@ -5741,6 +5818,8 @@ public class AccessibilityNodeInfo implements Parcelable { mRowSpan = 0; mHeading = false; mSelected = false; + mRowTitle = null; + mColumnTitle = null; } } diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java new file mode 100644 index 000000000000..7677b89dff96 --- /dev/null +++ b/core/java/android/window/DisplayWindowPolicyController.java @@ -0,0 +1,84 @@ +/* + * 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 android.window; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; + +import java.io.PrintWriter; +import java.util.List; + +/** + * Abstract class to control the policies of the windows that can be displayed on the virtual + * display. + * + * @hide + */ +public abstract class DisplayWindowPolicyController { + /** + * The window flags that we are interested in. + * @see android.view.WindowManager.LayoutParams + * @see #keepActivityOnWindowFlagsChanged + */ + private int mWindowFlags; + + /** + * Returns {@code true} if the given window flags contain the flags that we're interested in. + */ + public final boolean isInterestedWindowFlags(int windowFlags) { + return (mWindowFlags & windowFlags) != 0; + } + + /** + * Sets the window flags that we’re interested in and expected + * #keepActivityOnWindowFlagsChanged to be called if any changes. + */ + public final void setInterestedWindowFlags(int windowFlags) { + mWindowFlags = windowFlags; + } + + /** + * Returns {@code true} if the given activities can be displayed on this virtual display. + */ + public abstract boolean canContainActivities(@NonNull List<ActivityInfo> activities); + + /** + * Called when an Activity window is layouted with the new changes where contains the + * window flags that we’re interested in. + * Returns {@code false} if the Activity cannot remain on the display and the activity task will + * be moved back to default display. + */ + public abstract boolean keepActivityOnWindowFlagsChanged( + ActivityInfo activityInfo, int windowFlags); + + /** + * This is called when the top activity of the display is changed. + */ + public void onTopActivityChanged(ComponentName topActivity, int uid) {} + + /** + * This is called when the apps that contains running activities on the display has changed. + */ + public void onRunningAppsChanged(int[] runningUids) {} + + /** Dump debug data */ + public void dump(String prefix, final PrintWriter pw) { + pw.println(prefix + "DisplayWindowPolicyController{" + super.toString() + "}"); + pw.println(prefix + " mWindowFlags=" + mWindowFlags); + } +} diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl index 5eb432e785ee..cdfa206423c2 100644 --- a/core/java/android/window/ITaskFragmentOrganizer.aidl +++ b/core/java/android/window/ITaskFragmentOrganizer.aidl @@ -19,12 +19,11 @@ package android.window; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; /** @hide */ oneway interface ITaskFragmentOrganizer { - void onTaskFragmentAppeared(in TaskFragmentAppearedInfo taskFragmentAppearedInfo); + void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo); diff --git a/core/java/android/window/TaskFragmentAppearedInfo.java b/core/java/android/window/TaskFragmentAppearedInfo.java deleted file mode 100644 index 89d9a9508a71..000000000000 --- a/core/java/android/window/TaskFragmentAppearedInfo.java +++ /dev/null @@ -1,93 +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 android.window; - -import android.annotation.NonNull; -import android.annotation.TestApi; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.SurfaceControl; - -/** - * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer. - * @hide - */ -@TestApi -public final class TaskFragmentAppearedInfo implements Parcelable { - - @NonNull - private final TaskFragmentInfo mTaskFragmentInfo; - - @NonNull - private final SurfaceControl mLeash; - - /** @hide */ - public TaskFragmentAppearedInfo( - @NonNull TaskFragmentInfo taskFragmentInfo, @NonNull SurfaceControl leash) { - mTaskFragmentInfo = taskFragmentInfo; - mLeash = leash; - } - - @NonNull - public TaskFragmentInfo getTaskFragmentInfo() { - return mTaskFragmentInfo; - } - - @NonNull - public SurfaceControl getLeash() { - return mLeash; - } - - private TaskFragmentAppearedInfo(Parcel in) { - mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR); - mLeash = in.readTypedObject(SurfaceControl.CREATOR); - } - - /** @hide */ - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeTypedObject(mTaskFragmentInfo, flags); - dest.writeTypedObject(mLeash, flags); - } - - @NonNull - public static final Creator<TaskFragmentAppearedInfo> CREATOR = - new Creator<TaskFragmentAppearedInfo>() { - @Override - public TaskFragmentAppearedInfo createFromParcel(Parcel in) { - return new TaskFragmentAppearedInfo(in); - } - - @Override - public TaskFragmentAppearedInfo[] newArray(int size) { - return new TaskFragmentAppearedInfo[size]; - } - }; - - @Override - public String toString() { - return "TaskFragmentAppearedInfo{" - + " taskFragmentInfo=" + mTaskFragmentInfo - + "}"; - } - - /** @hide */ - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 337c5a14e9d3..7e7d37083b5b 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -120,8 +120,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** Called when a TaskFragment is created and organized by this organizer. */ - public void onTaskFragmentAppeared( - @NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {} + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {} /** Called when the status of an organized TaskFragment is changed. */ public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {} @@ -169,7 +168,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() { @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) { + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { mExecutor.execute( () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo)); } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index fd9ad0d9bcfd..359c382f51bc 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -186,6 +186,17 @@ public class ChooserActivity extends ResolverActivity implements = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP"; /** + * Boolean extra added to "unbundled Sharesheet" delegation intents to signal whether the app + * prediction service is available. Our query of the service <em>availability</em> depends on + * privileges that are only available in the system, even though the service itself would then + * be available to the unbundled component. For now, we just include the query result as part of + * the handover intent. + * TODO: investigate whether the privileged query is necessary to determine the availability. + */ + protected static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE = + "com.android.internal.app.ChooserActivity.EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE"; + + /** * Transition name for the first image preview. * To be used for shared element transition into this activity. * @hide @@ -757,6 +768,11 @@ public class ChooserActivity extends ResolverActivity implements delegationIntent.setComponent(delegateActivity); delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent()); delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken); + + // Query prediction availability; mIsAppPredictorComponentAvailable isn't initialized. + delegationIntent.putExtra( + EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE, isAppPredictionServiceAvailable()); + delegationIntent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); // Don't close until the delegate finishes, or the token will be invalidated. @@ -971,7 +987,8 @@ public class ChooserActivity extends ResolverActivity implements return false; } - // Check if the app prediction component actually exists on the device. + // Check if the app prediction component actually exists on the device. The component is + // only visible when this is running in a system activity; otherwise this check will fail. Intent intent = new Intent(); intent.setComponent(appPredictionComponentName); if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d3ee98a01951..601280a25873 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6395,6 +6395,10 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> + <service android:name="com.android.server.compos.IsolatedCompilationJobService" + android:permission="android.permission.BIND_JOB_SERVICE"> + </service> + <service android:name="com.android.server.PruneInstantAppsJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2c60fbd8f207..a36785f37d3f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4943,9 +4943,10 @@ the app in the letterbox mode. --> <item name="config_fixedOrientationLetterboxAspectRatio" format="float" type="dimen">0.0</item> - <!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and - min between device bottom corner radii will be used instead. --> - <integer name="config_letterboxActivityCornersRadius">-1</integer> + <!-- Corners radius for activity presented the letterbox mode. Values < 0 enable rounded + corners with radius equal to min between device bottom corner radii. Default 0 value turns + off rounded corners logic in LetterboxUiController. --> + <integer name="config_letterboxActivityCornersRadius">0</integer> <!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are ignored and 0 is used. --> diff --git a/data/etc/com.android.intentresolver.xml b/data/etc/com.android.intentresolver.xml index 0f1c4673562e..f4e94ad0e04b 100644 --- a/data/etc/com.android.intentresolver.xml +++ b/data/etc/com.android.intentresolver.xml @@ -18,5 +18,6 @@ <privapp-permissions package="com.android.intentresolver"> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.PACKAGE_USAGE_STATS"/> </privapp-permissions> </permissions> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 85ef270ac49d..df751fc9fa48 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -27,8 +27,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; -import android.view.SurfaceControl; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; @@ -51,9 +49,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); - /** Mapping from the client assigned unique token to the TaskFragment {@link SurfaceControl}. */ - private final Map<IBinder, SurfaceControl> mFragmentLeashes = new ArrayMap<>(); - /** * Mapping from the client assigned unique token to the TaskFragment parent * {@link Configuration}. @@ -67,7 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * Callback that notifies the controller about changes to task fragments. */ interface TaskFragmentCallback { - void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo); + void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, @@ -259,15 +254,12 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { } @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) { - final TaskFragmentInfo info = taskFragmentAppearedInfo.getTaskFragmentInfo(); - final IBinder fragmentToken = info.getFragmentToken(); - final SurfaceControl leash = taskFragmentAppearedInfo.getLeash(); - mFragmentInfos.put(fragmentToken, info); - mFragmentLeashes.put(fragmentToken, leash); + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); + mFragmentInfos.put(fragmentToken, taskFragmentInfo); if (mCallback != null) { - mCallback.onTaskFragmentAppeared(taskFragmentAppearedInfo); + mCallback.onTaskFragmentAppeared(taskFragmentInfo); } } @@ -284,7 +276,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { @Override public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); - mFragmentLeashes.remove(taskFragmentInfo.getFragmentToken()); mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken()); if (mCallback != null) { 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 68c19041940c..fe6c7ba3b24c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -37,7 +37,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; @@ -110,14 +109,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) { - TaskFragmentContainer container = getContainer( - taskFragmentAppearedInfo.getTaskFragmentInfo().getFragmentToken()); + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } - container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo()); + container.setInfo(taskFragmentInfo); if (container.isFinished()) { mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); } diff --git a/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml b/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml new file mode 100644 index 000000000000..329e5b9b31a0 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" android:lStar="35" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml index 94165a11eccb..22cd384e1be0 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml +++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml @@ -16,6 +16,6 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <solid android:color="@color/size_compat_hint_bubble"/> + <solid android:color="@color/size_compat_background"/> <corners android:radius="@dimen/size_compat_hint_corner_radius"/> </shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml index a8f0f76ef27f..af9063a94afb 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml +++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml @@ -20,6 +20,6 @@ android:viewportWidth="10" android:viewportHeight="8"> <path - android:fillColor="@color/size_compat_hint_bubble" + android:fillColor="@color/size_compat_background" android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml index 3e486df71f91..18caa3582537 100644 --- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml +++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml @@ -20,16 +20,16 @@ android:viewportWidth="48" android:viewportHeight="48"> <path - android:fillColor="#53534D" + android:fillColor="@color/size_compat_background" android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" /> <group android:translateX="12" android:translateY="12"> <path - android:fillColor="#E4E3DA" + android:fillColor="@color/size_compat_text" android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/> <path - android:fillColor="#E4E3DA" + android:fillColor="@color/size_compat_text" android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/> </group> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml new file mode 100644 index 000000000000..95decff24ac4 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/size_compat_background_ripple"> + <item android:drawable="@drawable/size_compat_restart_button"/> +</ripple>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml index 17347f627049..d0e7c42dbf8b 100644 --- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml +++ b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml @@ -40,7 +40,7 @@ android:padding="16dp" android:text="@string/restart_button_description" android:textAlignment="viewStart" - android:textColor="#E4E3DA" + android:textColor="@color/size_compat_text" android:textSize="14sp"/> <ImageView diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml index 47e76f061877..82ebee263a64 100644 --- a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml +++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml @@ -30,7 +30,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:src="@drawable/size_compat_restart_button" + android:src="@drawable/size_compat_restart_button_ripple" android:background="@android:color/transparent" android:contentDescription="@string/restart_button_description"/> diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index b25a2189cd4d..23a21724e43d 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -29,7 +29,10 @@ <color name="bubbles_light">#FFFFFF</color> <color name="bubbles_dark">@color/GM2_grey_800</color> <color name="bubbles_icon_tint">@color/GM2_grey_700</color> - <color name="size_compat_hint_bubble">#30312B</color> + + <!-- Size Compat Restart Button --> + <color name="size_compat_background">@android:color/system_neutral1_800</color> + <color name="size_compat_text">@android:color/system_neutral1_50</color> <!-- GM2 colors --> <color name="GM2_grey_200">#E8EAED</color> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index d239e56bfd69..9ceed243faa5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -244,15 +244,24 @@ public abstract class WMShellBaseModule { // Fullscreen // + // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride} + @BindsOptionalOf + @DynamicOverride + abstract FullscreenTaskListener optionalFullscreenTaskListener(); + @WMSingleton @Provides static FullscreenTaskListener provideFullscreenTaskListener( + @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener, SyncTransactionQueue syncQueue, Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController, - Optional<RecentTasksController> recentTasksOptional - ) { - return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController, - recentTasksOptional); + Optional<RecentTasksController> recentTasksOptional) { + if (fullscreenTaskListener.isPresent()) { + return fullscreenTaskListener.get(); + } else { + return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController, + recentTasksOptional); + } } // @@ -337,7 +346,7 @@ public abstract class WMShellBaseModule { @Provides static Optional<OneHandedController> providesOneHandedController( @DynamicOverride Optional<OneHandedController> oneHandedController) { - if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) { + if (SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) { return oneHandedController; } return Optional.empty(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 00083d986dbe..83390a539043 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -57,7 +57,7 @@ import java.lang.annotation.RetentionPolicy; public class TvPipController implements PipTransitionController.PipTransitionCallback, TvPipMenuController.Delegate, TvPipNotificationController.Delegate { private static final String TAG = "TvPipController"; - static final boolean DEBUG = true; + static final boolean DEBUG = false; private static final int NONEXISTENT_TASK_ID = -1; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 7cf3bafe499a..a006f308d694 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -111,6 +111,11 @@ public class RecentTasksController implements TaskStackListenerCallback, if (taskId1 == taskId2) { return; } + if (mSplitTasks.get(taskId1, INVALID_TASK_ID) == taskId2 + && mTaskSplitBoundsMap.get(taskId1).equals(splitBounds)) { + // If the two tasks are already paired and the bounds are the same, then skip updating + return; + } // Remove any previous pairs removeSplitPair(taskId1); removeSplitPair(taskId2); @@ -121,6 +126,7 @@ public class RecentTasksController implements TaskStackListenerCallback, mSplitTasks.put(taskId2, taskId1); mTaskSplitBoundsMap.put(taskId1, splitBounds); mTaskSplitBoundsMap.put(taskId2, splitBounds); + notifyRecentTasksChanged(); } /** @@ -133,6 +139,7 @@ public class RecentTasksController implements TaskStackListenerCallback, mSplitTasks.delete(pairedTaskId); mTaskSplitBoundsMap.remove(taskId); mTaskSplitBoundsMap.remove(pairedTaskId); + notifyRecentTasksChanged(); } } @@ -217,7 +224,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } final int pairedTaskId = mSplitTasks.get(taskInfo.taskId); - if (pairedTaskId != INVALID_TASK_ID) { + if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) { final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java index aadf792c572f..a0c84cc33ebd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java @@ -19,6 +19,8 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * Container of various information needed to display split screen * tasks/leashes/etc in Launcher @@ -93,6 +95,24 @@ public class StagedSplitBounds implements Parcelable { } @Override + public boolean equals(Object obj) { + if (!(obj instanceof StagedSplitBounds)) { + return false; + } + // Only need to check the base fields (the other fields are derived from these) + final StagedSplitBounds other = (StagedSplitBounds) obj; + return Objects.equals(leftTopBounds, other.leftTopBounds) + && Objects.equals(rightBottomBounds, other.rightBottomBounds) + && leftTopTaskId == other.leftTopTaskId + && rightBottomTaskId == other.rightBottomTaskId; + } + + @Override + public int hashCode() { + return Objects.hash(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId); + } + + @Override public String toString() { return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n" diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 19a5417aace6..50f6bd7b4927 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static java.lang.Integer.MAX_VALUE; @@ -83,6 +84,36 @@ public class RecentTasksControllerTest extends ShellTestCase { } @Test + public void testAddRemoveSplitNotifyChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + setRawList(t1, t2); + + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(StagedSplitBounds.class)); + verify(mRecentTasksController).notifyRecentTasksChanged(); + + reset(mRecentTasksController); + mRecentTasksController.removeSplitPair(t1.taskId); + verify(mRecentTasksController).notifyRecentTasksChanged(); + } + + @Test + public void testAddSameSplitBoundsInfoSkipNotifyChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + setRawList(t1, t2); + + // Verify only one update if the split info is the same + StagedSplitBounds bounds1 = new StagedSplitBounds(new Rect(0, 0, 50, 50), + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1); + StagedSplitBounds bounds2 = new StagedSplitBounds(new Rect(0, 0, 50, 50), + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2); + verify(mRecentTasksController, times(1)).notifyRecentTasksChanged(); + } + + @Test public void testGetRecentTasks() { ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp index 5aad821ad59f..6fc251dc815c 100644 --- a/libs/hwui/WebViewFunctorManager.cpp +++ b/libs/hwui/WebViewFunctorManager.cpp @@ -118,6 +118,24 @@ void WebViewFunctor::onRemovedFromTree() { } } +bool WebViewFunctor::prepareRootSurfaceControl() { + if (!Properties::enableWebViewOverlays) return false; + + renderthread::CanvasContext* activeContext = renderthread::CanvasContext::getActiveContext(); + if (!activeContext) return false; + + ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl(); + if (!rootSurfaceControl) return false; + + int32_t rgid = activeContext->getSurfaceControlGenerationId(); + if (mParentSurfaceControlGenerationId != rgid) { + reparentSurfaceControl(rootSurfaceControl); + mParentSurfaceControlGenerationId = rgid; + } + + return true; +} + void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) { ATRACE_NAME("WebViewFunctor::drawGl"); if (!mHasContext) { @@ -131,20 +149,8 @@ void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) { .mergeTransaction = currentFunctor.mergeTransaction, }; - if (Properties::enableWebViewOverlays && !drawInfo.isLayer) { - renderthread::CanvasContext* activeContext = - renderthread::CanvasContext::getActiveContext(); - if (activeContext != nullptr) { - ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl(); - if (rootSurfaceControl) { - overlayParams.overlaysMode = OverlaysMode::Enabled; - int32_t rgid = activeContext->getSurfaceControlGenerationId(); - if (mParentSurfaceControlGenerationId != rgid) { - reparentSurfaceControl(rootSurfaceControl); - mParentSurfaceControlGenerationId = rgid; - } - } - } + if (!drawInfo.isLayer && prepareRootSurfaceControl()) { + overlayParams.overlaysMode = OverlaysMode::Enabled; } mCallbacks.gles.draw(mFunctor, mData, drawInfo, overlayParams); @@ -170,7 +176,10 @@ void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) { .mergeTransaction = currentFunctor.mergeTransaction, }; - // TODO, enable surface control once offscreen mode figured out + if (!params.is_layer && prepareRootSurfaceControl()) { + overlayParams.overlaysMode = OverlaysMode::Enabled; + } + mCallbacks.vk.draw(mFunctor, mData, params, overlayParams); } diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h index f28f310993ec..0a02f2d4b720 100644 --- a/libs/hwui/WebViewFunctorManager.h +++ b/libs/hwui/WebViewFunctorManager.h @@ -88,6 +88,7 @@ public: } private: + bool prepareRootSurfaceControl(); void reparentSurfaceControl(ASurfaceControl* parent); private: diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp index 8abf4534a04c..e6ef95b9cf91 100644 --- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp @@ -72,6 +72,7 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { .clip_top = mClip.fTop, .clip_right = mClip.fRight, .clip_bottom = mClip.fBottom, + .is_layer = !vulkan_info.fFromSwapchainOrAndroidWindow, }; mat4.getColMajor(¶ms.transform[0]); params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer; diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h index 4ae0f5a0a2e5..5c596576df4e 100644 --- a/libs/hwui/private/hwui/DrawVkInfo.h +++ b/libs/hwui/private/hwui/DrawVkInfo.h @@ -68,6 +68,9 @@ struct VkFunctorDrawParams { int clip_top; int clip_right; int clip_bottom; + + // Input: Whether destination surface is offscreen surface. + bool is_layer; }; } // namespace uirenderer diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index fe9a30a59870..611a4d9c8f2c 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -426,7 +426,7 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { if (bufferInfo->skSurface.get() == nullptr) { bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer( mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()), - kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr); + kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true); if (bufferInfo->skSurface.get() == nullptr) { ALOGE("SkSurface::MakeFromAHardwareBuffer failed"); mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 8f04cfb70469..f43586f8d9d0 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -17,24 +17,29 @@ #define LOG_TAG "PointerController" //#define LOG_NDEBUG 0 -// Log debug messages about pointer updates -#define DEBUG_POINTER_UPDATES 0 - #include "PointerController.h" -#include "MouseCursorController.h" #include "PointerControllerContext.h" -#include "TouchSpotController.h" - -#include <log/log.h> -#include <SkBitmap.h> #include <SkBlendMode.h> #include <SkCanvas.h> #include <SkColor.h> -#include <SkPaint.h> namespace android { +namespace { + +const ui::Transform kIdentityTransform; + +} // namespace + +// --- PointerController::DisplayInfoListener --- + +void PointerController::DisplayInfoListener::onWindowInfosChanged( + const std::vector<android::gui::WindowInfo>&, + const std::vector<android::gui::DisplayInfo>& displayInfo) { + mPointerController.onDisplayInfosChanged(displayInfo); +} + // --- PointerController --- std::shared_ptr<PointerController> PointerController::create( @@ -63,9 +68,16 @@ std::shared_ptr<PointerController> PointerController::create( PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, const sp<SpriteController>& spriteController) - : mContext(policy, looper, spriteController, *this), mCursorController(mContext) { + : mContext(policy, looper, spriteController, *this), + mCursorController(mContext), + mDisplayInfoListener(new DisplayInfoListener(*this)) { std::scoped_lock lock(mLock); mLocked.presentation = Presentation::SPOT; + SurfaceComposerClient::getDefault()->addWindowInfosListener(mDisplayInfoListener); +} + +PointerController::~PointerController() { + SurfaceComposerClient::getDefault()->removeWindowInfosListener(mDisplayInfoListener); } bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX, @@ -74,7 +86,14 @@ bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX } void PointerController::move(float deltaX, float deltaY) { - mCursorController.move(deltaX, deltaY); + const int32_t displayId = mCursorController.getDisplayId(); + vec2 transformed; + { + std::scoped_lock lock(mLock); + const auto& transform = getTransformForDisplayLocked(displayId); + transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); + } + mCursorController.move(transformed.x, transformed.y); } void PointerController::setButtonState(int32_t buttonState) { @@ -86,12 +105,26 @@ int32_t PointerController::getButtonState() const { } void PointerController::setPosition(float x, float y) { - std::scoped_lock lock(mLock); - mCursorController.setPosition(x, y); + const int32_t displayId = mCursorController.getDisplayId(); + vec2 transformed; + { + std::scoped_lock lock(mLock); + const auto& transform = getTransformForDisplayLocked(displayId); + transformed = transform.transform(x, y); + } + mCursorController.setPosition(transformed.x, transformed.y); } void PointerController::getPosition(float* outX, float* outY) const { + const int32_t displayId = mCursorController.getDisplayId(); mCursorController.getPosition(outX, outY); + { + std::scoped_lock lock(mLock); + const auto& transform = getTransformForDisplayLocked(displayId); + const auto xy = transform.inverse().transform(*outX, *outY); + *outX = xy.x; + *outY = xy.y; + } } int32_t PointerController::getDisplayId() const { @@ -130,11 +163,25 @@ void PointerController::setPresentation(Presentation presentation) { void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { std::scoped_lock lock(mLock); + std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; + const ui::Transform& transform = getTransformForDisplayLocked(displayId); + + for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { + const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()]; + + const vec2 xy = transform.transform(spotCoords[index].getXYValue()); + outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); + outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y); + + float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); + outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); + } + auto it = mLocked.spotControllers.find(displayId); if (it == mLocked.spotControllers.end()) { mLocked.spotControllers.try_emplace(displayId, displayId, mContext); } - mLocked.spotControllers.at(displayId).setSpots(spotCoords, spotIdToIndex, spotIdBits); + mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits); } void PointerController::clearSpots() { @@ -194,7 +241,7 @@ void PointerController::doInactivityTimeout() { void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) { std::unordered_set<int32_t> displayIdSet; - for (DisplayViewport viewport : viewports) { + for (const DisplayViewport& viewport : viewports) { displayIdSet.insert(viewport.displayId); } @@ -214,4 +261,17 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& } } +void PointerController::onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfo) { + std::scoped_lock lock(mLock); + mLocked.mDisplayInfos = displayInfo; +} + +const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const { + const auto& di = mLocked.mDisplayInfos; + auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) { + return info.displayId == displayId; + }); + return it != di.end() ? it->transform : kIdentityTransform; +} + } // namespace android diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 97567bab202b..796077f6c38c 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -47,7 +47,7 @@ public: const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, const sp<SpriteController>& spriteController); - virtual ~PointerController() = default; + ~PointerController() override; virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; virtual void move(float deltaX, float deltaY); @@ -72,6 +72,8 @@ public: void reloadPointerResources(); void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports); + void onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfos); + private: friend PointerControllerContext::LooperCallback; friend PointerControllerContext::MessageHandler; @@ -85,9 +87,23 @@ private: struct Locked { Presentation presentation; + std::vector<gui::DisplayInfo> mDisplayInfos; std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; } mLocked GUARDED_BY(mLock); + class DisplayInfoListener : public gui::WindowInfosListener { + public: + explicit DisplayInfoListener(PointerController& pc) : mPointerController(pc){}; + void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&, + const std::vector<android::gui::DisplayInfo>&) override; + + private: + PointerController& mPointerController; + }; + sp<DisplayInfoListener> mDisplayInfoListener; + + const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(mLock); + PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, const sp<SpriteController>& spriteController); void clearSpotsLocked(); diff --git a/media/Android.bp b/media/Android.bp index 15b24b220768..fcdfd72c91d5 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -115,6 +115,7 @@ aidl_interface { aidl_interface { name: "android.media.soundtrigger.types", vendor_available: true, + host_supported: true, flags: [ "-Werror", "-Weverything", diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl index c4f82ebc830f..2f165f0de7e5 100644 --- a/media/java/android/media/tv/interactive/ITvIAppService.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl @@ -16,6 +16,7 @@ package android.media.tv.interactive; +import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppSessionCallback; /** @@ -24,5 +25,7 @@ import android.media.tv.interactive.ITvIAppSessionCallback; * @hide */ oneway interface ITvIAppService { + void registerCallback(in ITvIAppServiceCallback callback); + void unregisterCallback(in ITvIAppServiceCallback callback); void createSession(in ITvIAppSessionCallback callback, in String iAppServiceId, int type); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java index 8863729bfab3..78b8173e0af7 100644 --- a/media/java/android/media/tv/interactive/TvIAppService.java +++ b/media/java/android/media/tv/interactive/TvIAppService.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; import android.view.KeyEvent; @@ -62,11 +63,26 @@ public abstract class TvIAppService extends Service { public static final String SERVICE_META_DATA = "android.media.tv.interactive.app"; private final Handler mServiceHandler = new ServiceHandler(); + private final RemoteCallbackList<ITvIAppServiceCallback> mCallbacks = + new RemoteCallbackList<>(); /** @hide */ @Override public final IBinder onBind(Intent intent) { ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() { + @Override + public void registerCallback(ITvIAppServiceCallback cb) { + if (cb != null) { + mCallbacks.register(cb); + } + } + + @Override + public void unregisterCallback(ITvIAppServiceCallback cb) { + if (cb != null) { + mCallbacks.unregister(cb); + } + } @Override public void createSession(ITvIAppSessionCallback cb, String iAppServiceId, int type) { @@ -137,7 +153,7 @@ public abstract class TvIAppService extends Service { * Called when the application sets the surface. * * <p>The TV IApp service should render interactive app UI onto the given surface. When - * called with {@code null}, the input service should immediately free any references to the + * called with {@code null}, the IApp service should immediately free any references to the * currently set surface and stop using it. * * @param surface The surface to be used for interactive app UI rendering. Can be diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index ae2711200afd..f0f576e90d91 100644 --- a/media/java/android/media/tv/tuner/filter/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -251,8 +251,8 @@ public class Filter implements AutoCloseable { private native int nativeFlushFilter(); private native int nativeRead(byte[] buffer, long offset, long size); private native int nativeClose(); - private native String nativeCreateSharedFilter(); - private native void nativeReleaseSharedFilter(String token); + private native String nativeAcquireSharedFilterToken(); + private native void nativeFreeSharedFilterToken(String token); // Called by JNI private Filter(long id) { @@ -562,20 +562,20 @@ public class Filter implements AutoCloseable { } /** - * Creates a shared filter. + * Acquires a shared filter token. * * @return a string shared filter token. */ @Nullable - public String createSharedFilter() { + public String acquireSharedFilterToken() { synchronized (mLock) { TunerUtils.checkResourceState(TAG, mIsClosed); if (mIsStarted || mIsShared) { - Log.d(TAG, "Create shared filter in a wrong state, started: " + + Log.d(TAG, "Acquire shared filter in a wrong state, started: " + mIsStarted + "shared: " + mIsShared); return null; } - String token = nativeCreateSharedFilter(); + String token = nativeAcquireSharedFilterToken(); if (token != null) { mIsShared = true; } @@ -584,17 +584,17 @@ public class Filter implements AutoCloseable { } /** - * Releases a shared filter. + * Frees a shared filter token. * * @param filterToken the token of the shared filter being released. */ - public void releaseSharedFilter(@NonNull String filterToken) { + public void freeSharedFilterToken(@NonNull String filterToken) { synchronized (mLock) { TunerUtils.checkResourceState(TAG, mIsClosed); if (!mIsShared) { return; } - nativeReleaseSharedFilter(filterToken); + nativeFreeSharedFilterToken(filterToken); mIsShared = false; } } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index c4dfee42c3cd..9526c52e3250 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -3890,22 +3890,22 @@ static jint android_media_tv_Tuner_close_filter(JNIEnv *env, jobject filter) { return (jint)r; } -static jstring android_media_tv_Tuner_create_shared_filter(JNIEnv *env, jobject filter) { +static jstring android_media_tv_Tuner_acquire_shared_filter_token(JNIEnv *env, jobject filter) { sp<FilterClient> filterClient = getFilterClient(env, filter); if (filterClient == nullptr) { jniThrowException(env, "java/lang/IllegalStateException", - "Failed to create shared filter: filter client not found"); + "Failed to acquire shared filter token: filter client not found"); return nullptr; } - string token = filterClient->createSharedFilter(); + string token = filterClient->acquireSharedFilterToken(); if (token.empty()) { return nullptr; } return env->NewStringUTF(token.data()); } -static void android_media_tv_Tuner_release_shared_filter( +static void android_media_tv_Tuner_free_shared_filter_token( JNIEnv *env, jobject filter, jstring token) { sp<FilterClient> filterClient = getFilterClient(env, filter); if (filterClient == nullptr) { @@ -3915,7 +3915,7 @@ static void android_media_tv_Tuner_release_shared_filter( } std::string filterToken(env->GetStringUTFChars(token, nullptr)); - filterClient->releaseSharedFilter(filterToken); + filterClient->freeSharedFilterToken(filterToken); } static sp<TimeFilterClient> getTimeFilterClient(JNIEnv *env, jobject filter) { @@ -4408,10 +4408,10 @@ static const JNINativeMethod gFilterMethods[] = { { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter }, { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq }, { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter }, - {"nativeCreateSharedFilter", "()Ljava/lang/String;", - (void *)android_media_tv_Tuner_create_shared_filter}, - {"nativeReleaseSharedFilter", "(Ljava/lang/String;)V", - (void *)android_media_tv_Tuner_release_shared_filter}, + {"nativeAcquireSharedFilterToken", "()Ljava/lang/String;", + (void *)android_media_tv_Tuner_acquire_shared_filter_token}, + {"nativeFreeSharedFilterToken", "(Ljava/lang/String;)V", + (void *)android_media_tv_Tuner_free_shared_filter_token}, }; static const JNINativeMethod gSharedFilterMethods[] = { diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp index e8b3de82f284..bd283dce60a5 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -196,10 +196,10 @@ Result FilterClient::close() { return Result::INVALID_STATE; } -string FilterClient::createSharedFilter() { +string FilterClient::acquireSharedFilterToken() { if (mTunerFilter != nullptr) { string filterToken; - if (mTunerFilter->createSharedFilter(&filterToken).isOk()) { + if (mTunerFilter->acquireSharedFilterToken(&filterToken).isOk()) { return filterToken; } } @@ -207,9 +207,9 @@ string FilterClient::createSharedFilter() { return ""; } -Result FilterClient::releaseSharedFilter(const string& filterToken) { +Result FilterClient::freeSharedFilterToken(const string& filterToken) { if (mTunerFilter != nullptr) { - Status s = mTunerFilter->releaseSharedFilter(filterToken); + Status s = mTunerFilter->freeSharedFilterToken(filterToken); return ClientHelper::getServiceSpecificErrorCode(s); } diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h index c031b2ac0b5b..7ebe7bc76c6f 100644 --- a/media/jni/tuner/FilterClient.h +++ b/media/jni/tuner/FilterClient.h @@ -143,14 +143,14 @@ public: Result close(); /** - * Create a new SharedFiler. + * Accquire a new SharedFiler token. */ - string createSharedFilter(); + string acquireSharedFilterToken(); /** - * Release SharedFiler. + * Release SharedFiler token. */ - Result releaseSharedFilter(const string& filterToken); + Result freeSharedFilterToken(const string& filterToken); private: Result getFilterMq(); diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp new file mode 100644 index 000000000000..2b81200f0079 --- /dev/null +++ b/omapi/aidl/Android.bp @@ -0,0 +1,35 @@ +// Copyright 2020, 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +aidl_interface { + name: "android.se.omapi", + vendor_available: true, + srcs: ["android/se/omapi/*.aidl"], + stability: "vintf", + backend: { + java: { + sdk_version: "module_current", + }, + rust: { + enabled: true, + }, + ndk: { + separate_platform_variant: false, + }, + }, +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl new file mode 100644 index 000000000000..725013a35cde --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl @@ -0,0 +1,46 @@ +/* + * 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. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementChannel { + void close(); + boolean isClosed(); + boolean isBasicChannel(); + byte[] getSelectResponse(); + byte[] transmit(in byte[] command); + boolean selectNext(); +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl new file mode 100644 index 000000000000..77e1c53f47ac --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017, 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. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementListener { +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl new file mode 100644 index 000000000000..2b10c473c902 --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017, 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. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementReader { + boolean isSecureElementPresent(); + android.se.omapi.ISecureElementSession openSession(); + void closeSessions(); + boolean reset(); +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl new file mode 100644 index 000000000000..ae6346278691 --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017, 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. + *//* + * Copyright (c) 2015-2017, The Linux Foundation. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementService { + String[] getReaders(); + android.se.omapi.ISecureElementReader getReader(in String reader); + boolean[] isNFCEventAllowed(in String reader, in byte[] aid, in String[] packageNames); +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl new file mode 100644 index 000000000000..06287c551f5c --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017, 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. + *//* + * Copyright (c) 2015-2017, The Linux Foundation. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementSession { + byte[] getAtr(); + void close(); + void closeChannels(); + boolean isClosed(); + android.se.omapi.ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2, in android.se.omapi.ISecureElementListener listener); + android.se.omapi.ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2, in android.se.omapi.ISecureElementListener listener); +} diff --git a/core/java/android/se/omapi/ISecureElementChannel.aidl b/omapi/aidl/android/se/omapi/ISecureElementChannel.aidl index 4ae57ab829cb..bbd3c148caaf 100644 --- a/core/java/android/se/omapi/ISecureElementChannel.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementChannel.aidl @@ -22,6 +22,7 @@ package android.se.omapi; import android.se.omapi.ISecureElementSession; /** @hide */ +@VintfStability interface ISecureElementChannel { /** @@ -58,6 +59,9 @@ interface ISecureElementChannel { * Transmits the specified command APDU and returns the response APDU. * MANAGE channel commands are not supported. * Selection of applets is not supported in logical channels. + * + * @param command Command APDU, its structure is defined in ISO/IEC 7816-4 + * in Standard byte format */ byte[] transmit(in byte[] command); diff --git a/core/java/android/se/omapi/ISecureElementListener.aidl b/omapi/aidl/android/se/omapi/ISecureElementListener.aidl index e9dd18181c56..479dcd7d5acf 100644 --- a/core/java/android/se/omapi/ISecureElementListener.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementListener.aidl @@ -23,5 +23,6 @@ package android.se.omapi; * Interface to receive call-backs when the service is connected. * @hide */ +@VintfStability interface ISecureElementListener { } diff --git a/core/java/android/se/omapi/ISecureElementReader.aidl b/omapi/aidl/android/se/omapi/ISecureElementReader.aidl index 41244ab058e0..a6979face61f 100644 --- a/core/java/android/se/omapi/ISecureElementReader.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementReader.aidl @@ -22,6 +22,7 @@ package android.se.omapi; import android.se.omapi.ISecureElementSession; /** @hide */ +@VintfStability interface ISecureElementReader { /** @@ -34,7 +35,7 @@ interface ISecureElementReader { * Connects to a secure element in this reader. <br> * This method prepares (initialises) the Secure Element for communication * before the Session object is returned (e.g. powers the Secure Element by - * ICC ON if its not already on). There might be multiple sessions opened at + * ICC ON if it is not already on). There might be multiple sessions opened at * the same time on the same reader. The system ensures the interleaving of * APDUs between the respective sessions. * diff --git a/core/java/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/android/se/omapi/ISecureElementService.aidl index 4fa799e78757..61ae4816de82 100644 --- a/core/java/android/se/omapi/ISecureElementService.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementService.aidl @@ -28,23 +28,31 @@ import android.se.omapi.ISecureElementReader; * SecureElement service interface. * @hide */ +@VintfStability interface ISecureElementService { /** * Returns the friendly names of available Secure Element readers. + * <ul> + * <li>If the reader is a SIM reader, then its name must be "SIM[Slot]".</li> + * <li>If the reader is a SD or micro SD reader, then its name must be "SD[Slot]"</li> + * <li>If the reader is a embedded SE reader, then its name must be "eSE[Slot]"</li> + * </ul> + * Slot is a decimal number without leading zeros. The Numbering must start with 1 + * (e.g. SIM1, SIM2, ... or SD1, SD2, ... or eSE1, eSE2, ...). */ String[] getReaders(); /** * Returns SecureElement Service reader object to the given name. */ - ISecureElementReader getReader(String reader); + ISecureElementReader getReader(in String reader); /** * Checks if the application defined by the package name is allowed to * receive NFC transaction events for the defined AID. */ - boolean[] isNFCEventAllowed(String reader, in byte[] aid, + boolean[] isNFCEventAllowed(in String reader, in byte[] aid, in String[] packageNames); } diff --git a/core/java/android/se/omapi/ISecureElementSession.aidl b/omapi/aidl/android/se/omapi/ISecureElementSession.aidl index 8ea599f2e866..129ecc4ddaa3 100644 --- a/core/java/android/se/omapi/ISecureElementSession.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementSession.aidl @@ -27,6 +27,7 @@ import android.se.omapi.ISecureElementReader; import android.se.omapi.ISecureElementListener; /** @hide */ +@VintfStability interface ISecureElementSession { /** @@ -45,7 +46,6 @@ interface ISecureElementSession { */ void closeChannels(); - /** * Tells if this session is closed. * @@ -59,15 +59,19 @@ interface ISecureElementSession { * applet if aid != null. * Logical channels cannot be opened with this connection. * Use interface method openLogicalChannel() to open a logical channel. + * Listener is passed to secure element service and used to monitor whether + * the client application that uses OMAPI is still alive or not. */ ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2, - ISecureElementListener listener); + in ISecureElementListener listener); /** * Opens a connection using the next free logical channel of the card in the * specified reader. Selects the specified applet. * Selection of other applets with this connection is not supported. + * Listener is passed to secure element service and used to monitor whether + * the client application that uses OMAPI is still alive or not. */ ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2, - ISecureElementListener listener); + in ISecureElementListener listener); } diff --git a/omapi/aidl/vts/functional/AccessControlApp/Android.bp b/omapi/aidl/vts/functional/AccessControlApp/Android.bp new file mode 100644 index 000000000000..f03c3f6eb647 --- /dev/null +++ b/omapi/aidl/vts/functional/AccessControlApp/Android.bp @@ -0,0 +1,54 @@ +// +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "VtsHalOmapiSeAccessControlTestCases", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsHalOmapiSeAccessControlTestCases.cpp", + ], + shared_libs: [ + "libbase", + "liblog", + "libcutils", + "libhidlbase", + "libnativehelper", + "libutils", + "libbinder_ndk", + ], + static_libs: [ + "VtsHalHidlTargetTestBase", + "android.se.omapi-V1-ndk", + ], + cflags: [ + "-O0", + "-g", + "-Wall", + "-Werror", + ], + require_root: true, + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp b/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp new file mode 100644 index 000000000000..9ea65431417a --- /dev/null +++ b/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp @@ -0,0 +1,428 @@ +/* + * 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. + */ + +#include <aidl/android/se/omapi/BnSecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementChannel.h> +#include <aidl/android/se/omapi/ISecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementReader.h> +#include <aidl/android/se/omapi/ISecureElementService.h> +#include <aidl/android/se/omapi/ISecureElementSession.h> + +#include <VtsCoreUtil.h> +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <android-base/logging.h> +#include <android/binder_manager.h> +#include <binder/IServiceManager.h> +#include <cutils/properties.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> +#include <utils/String16.h> + +using namespace std; +using namespace ::testing; +using namespace android; + +int main(int argc, char** argv) { + InitGoogleTest(&argc, argv); + int status = RUN_ALL_TESTS(); + return status; +} + +namespace { + +class OMAPISEAccessControlTest : public TestWithParam<std::string> { + protected: + + class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {}; + + /** + * Verifies TLV data + * + * @return true if the data is tlv formatted, false otherwise + */ + bool verifyBerTlvData(std::vector<uint8_t> tlv) { + if (tlv.size() == 0) { + LOG(ERROR) << "Invalid tlv, null"; + return false; + } + int i = 0; + if ((tlv[i++] & 0x1F) == 0x1F) { + // extra byte for TAG field + i++; + } + + int len = tlv[i++] & 0xFF; + if (len > 127) { + // more than 1 byte for length + int bytesLength = len - 128; + len = 0; + for (int j = bytesLength; j > 0; j--) { + len += (len << 8) + (tlv[i++] & 0xFF); + } + } + // Additional 2 bytes for the SW + return (tlv.size() == (i + len + 2)); + } + + void testSelectableAid( + std::vector<std::vector<uint8_t>> authorizedAids) { + for (auto aid : authorizedAids) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::vector<uint8_t> selectResponse = {}; + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + ASSERT_TRUE( + verifyBerTlvData(selectResponse)) << "Select Response is not complete"; + } + } + } + } + + void testUnauthorisedAid( + std::vector<std::vector<uint8_t>> unAuthorizedAids) { + for (auto aid : unAuthorizedAids) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + if (!res.isOk()) { + ASSERT_EQ(res.getExceptionCode(), EX_SECURITY); + ASSERT_FALSE(res.isOk()) << "expected failed status for this test"; + } + } + } + } + } + + void testTransmitAPDU( + std::vector<uint8_t> aid, + std::vector<std::vector<uint8_t>> apdus) { + for (auto apdu : apdus) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + ASSERT_NE(reader, nullptr) << "reader is null"; + bool status = false; + std::vector<uint8_t> selectResponse = {}; + std::vector<uint8_t> transmitResponse = {}; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + ASSERT_TRUE( + verifyBerTlvData(selectResponse)) << "Select Response is not complete"; + + res = channel->transmit(apdu, &transmitResponse); + LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode() + << " Message: " << res.getMessage(); + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + ASSERT_TRUE(res.isOk()) << "failed to transmit"; + } + } + } + } + + void testUnauthorisedAPDU( + std::vector<uint8_t> aid, + std::vector<std::vector<uint8_t>> apdus) { + for (auto apdu : apdus) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + ASSERT_NE(reader, nullptr) << "reader is null"; + bool status = false; + std::vector<uint8_t> selectResponse = {}; + std::vector<uint8_t> transmitResponse = {}; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + ASSERT_TRUE( + verifyBerTlvData(selectResponse)) << "Select Response is not complete"; + + res = channel->transmit(apdu, &transmitResponse); + LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode() + << " Message: " << res.getMessage(); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + if (!res.isOk()) { + ASSERT_EQ(res.getExceptionCode(), EX_SECURITY); + ASSERT_FALSE(res.isOk()) << "expected failed status for this test"; + } + } + } + } + } + + bool supportOMAPIReaders() { + return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())); + } + + void getFirstApiLevel(int32_t* outApiLevel) { + int32_t firstApiLevel = property_get_int32(FEATURE_SE_API_LEVEL.c_str(), -1); + if (firstApiLevel < 0) { + firstApiLevel = property_get_int32(FEATURE_SE_SDK_VERSION.c_str(), -1); + } + ASSERT_GT(firstApiLevel, 0); // first_api_level must exist + *outApiLevel = firstApiLevel; + return; + } + + bool supportsHardware() { + bool lowRamDevice = property_get_bool(FEATURE_SE_LOW_RAM.c_str(), true); + return !lowRamDevice || deviceSupportsFeature(FEATURE_SE_HARDWARE_WATCH.c_str()) || + deviceSupportsFeature(FEATURE_SE_OMAPI_SERVICE.c_str()); // android.se.omapi + } + + void SetUp() override { + ASSERT_TRUE(supportsHardware()); + int32_t apiLevel; + getFirstApiLevel(&apiLevel); + ASSERT_TRUE(apiLevel > 27); + ASSERT_TRUE(supportOMAPIReaders()); + LOG(INFO) << "get OMAPI service with name:" << GetParam(); + ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str())); + mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder); + ASSERT_TRUE(mOmapiSeService); + + std::vector<std::string> readers = {}; + + if (mOmapiSeService != NULL) { + auto status = mOmapiSeService->getReaders(&readers); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + for (auto readerName : readers) { + // Filter eSE readers only + if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) { + std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader; + status = mOmapiSeService->getReader(readerName, &reader); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + mVSReaders[readerName] = reader; + } + } + } + } + + void TearDown() override { + if (mOmapiSeService != nullptr) { + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + reader->closeSessions(); + } + } + } + } + + static inline std::string const ESE_READER_PREFIX = "eSE"; + static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese"; + static inline std::string const FEATURE_SE_LOW_RAM = "ro.config.low_ram"; + static inline std::string const FEATURE_SE_HARDWARE_WATCH = "android.hardware.type.watch"; + static inline std::string const FEATURE_SE_OMAPI_SERVICE = "com.android.se"; + static inline std::string const FEATURE_SE_SDK_VERSION = "ro.build.version.sdk"; + static inline std::string const FEATURE_SE_API_LEVEL = "ro.product.first_api_level"; + + std::vector<uint8_t> AID_40 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x40}; + std::vector<uint8_t> AID_41 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x41}; + std::vector<uint8_t> AID_42 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x42}; + std::vector<uint8_t> AID_43 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x43}; + std::vector<uint8_t> AID_44 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x44}; + std::vector<uint8_t> AID_45 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x45}; + std::vector<uint8_t> AID_46 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x46}; + std::vector<uint8_t> AID_47 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x47}; + std::vector<uint8_t> AID_48 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x48}; + std::vector<uint8_t> AID_49 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x49}; + std::vector<uint8_t> AID_4A = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4A}; + std::vector<uint8_t> AID_4B = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4B}; + std::vector<uint8_t> AID_4C = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4C}; + std::vector<uint8_t> AID_4D = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4D}; + std::vector<uint8_t> AID_4E = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4E}; + std::vector<uint8_t> AID_4F = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4F}; + + std::vector<std::vector<uint8_t>> AUTHORIZED_AID = {AID_40, AID_41, AID_42, AID_44, AID_45, + AID_47, AID_48, AID_49, AID_4A, AID_4B, + AID_4C, AID_4D, AID_4E, AID_4F}; + std::vector<std::vector<uint8_t>> UNAUTHORIZED_AID = {AID_43, AID_46}; + + /* Authorized APDU for AID_40 */ + std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_40 = { + {0x00, 0x06, 0x00, 0x00}, + {0xA0, 0x06, 0x00, 0x00}, + }; + /* Unauthorized APDU for AID_40 */ + std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_40 = { + {0x00, 0x08, 0x00, 0x00, 0x00}, + {0x80, 0x06, 0x00, 0x00}, + {0xA0, 0x08, 0x00, 0x00, 0x00}, + {0x94, 0x06, 0x00, 0x00, 0x00}, + }; + + /* Authorized APDU for AID_41 */ + std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_41 = { + {0x94, 0x06, 0x00, 0x00}, + {0x94, 0x08, 0x00, 0x00, 0x00}, + {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}}; + /* Unauthorized APDU for AID_41 */ + std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_41 = { + {0x00, 0x06, 0x00, 0x00}, + {0x80, 0x06, 0x00, 0x00}, + {0xA0, 0x06, 0x00, 0x00}, + {0x00, 0x08, 0x00, 0x00, 0x00}, + {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0x80, 0x08, 0x00, 0x00, 0x00}, + {0xA0, 0x08, 0x00, 0x00, 0x00}, + {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + }; + + std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService; + + std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> + mVSReaders = {}; +}; + +TEST_P(OMAPISEAccessControlTest, TestAuthorizedAID) { + testSelectableAid(AUTHORIZED_AID); +} + +TEST_P(OMAPISEAccessControlTest, TestUnauthorizedAID) { + testUnauthorisedAid(UNAUTHORIZED_AID); +} + +TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID40) { + testTransmitAPDU(AID_40, AUTHORIZED_APDU_AID_40); +} + +TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID40) { + testUnauthorisedAPDU(AID_40, UNAUTHORIZED_APDU_AID_40); +} + +TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID41) { + testTransmitAPDU(AID_41, AUTHORIZED_APDU_AID_41); +} + +TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID41) { + testUnauthorisedAPDU(AID_41, UNAUTHORIZED_APDU_AID_41); +} + +INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEAccessControlTest, + testing::ValuesIn(::android::getAidlHalInstanceNames( + aidl::android::se::omapi::ISecureElementService::descriptor)), + android::hardware::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEAccessControlTest); + +} // namespace diff --git a/omapi/aidl/vts/functional/omapi/Android.bp b/omapi/aidl/vts/functional/omapi/Android.bp new file mode 100644 index 000000000000..c3ab8d13920c --- /dev/null +++ b/omapi/aidl/vts/functional/omapi/Android.bp @@ -0,0 +1,54 @@ +// +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "VtsHalOmapiSeServiceV1_TargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsHalOmapiSeServiceV1_TargetTest.cpp", + ], + shared_libs: [ + "libbase", + "liblog", + "libcutils", + "libhidlbase", + "libnativehelper", + "libutils", + "libbinder_ndk", + ], + static_libs: [ + "VtsHalHidlTargetTestBase", + "android.se.omapi-V1-ndk", + ], + cflags: [ + "-O0", + "-g", + "-Wall", + "-Werror", + ], + require_root: true, + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp new file mode 100644 index 000000000000..319cb7e70884 --- /dev/null +++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp @@ -0,0 +1,609 @@ +/* + * 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. + */ + +#include <aidl/android/se/omapi/BnSecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementChannel.h> +#include <aidl/android/se/omapi/ISecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementReader.h> +#include <aidl/android/se/omapi/ISecureElementService.h> +#include <aidl/android/se/omapi/ISecureElementSession.h> + +#include <VtsCoreUtil.h> +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <android-base/logging.h> +#include <android/binder_manager.h> +#include <binder/IServiceManager.h> +#include <cutils/properties.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> +#include <utils/String16.h> + +using namespace std; +using namespace ::testing; +using namespace android; + +int main(int argc, char** argv) { + InitGoogleTest(&argc, argv); + int status = RUN_ALL_TESTS(); + return status; +} + +namespace { + +class OMAPISEServiceHalTest : public TestWithParam<std::string> { + protected: + class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {}; + + void testSelectableAid( + std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader, + std::vector<uint8_t> aid, std::vector<uint8_t>& selectResponse) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + } + + void testNonSelectableAid( + std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader, + std::vector<uint8_t> aid) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + LOG(ERROR) << res.getMessage(); + ASSERT_FALSE(res.isOk()) << "expected to fail to open channel for this test"; + } + + /** + * Verifies TLV data + * + * @return true if the data is tlv formatted, false otherwise + */ + bool verifyBerTlvData(std::vector<uint8_t> tlv) { + if (tlv.size() == 0) { + LOG(ERROR) << "Invalid tlv, null"; + return false; + } + int i = 0; + if ((tlv[i++] & 0x1F) == 0x1F) { + // extra byte for TAG field + i++; + } + + int len = tlv[i++] & 0xFF; + if (len > 127) { + // more than 1 byte for length + int bytesLength = len - 128; + len = 0; + for (int j = bytesLength; j > 0; j--) { + len += (len << 8) + (tlv[i++] & 0xFF); + } + } + // Additional 2 bytes for the SW + return (tlv.size() == (i + len + 2)); + } + + void internalTransmitApdu( + std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader, + std::vector<uint8_t> apdu, std::vector<uint8_t>& transmitResponse) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + std::vector<uint8_t> selectResponse = {}; + + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(SELECTABLE_AID, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + + res = channel->transmit(apdu, &transmitResponse); + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode() + << " Message: " << res.getMessage(); + ASSERT_TRUE(res.isOk()) << "failed to transmit"; + } + + bool supportOMAPIReaders() { + return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())); + } + + void SetUp() override { + LOG(INFO) << "get OMAPI service with name:" << GetParam(); + ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str())); + mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder); + ASSERT_TRUE(mOmapiSeService); + + std::vector<std::string> readers = {}; + + if (omapiSecureService() != NULL) { + auto status = omapiSecureService()->getReaders(&readers); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + for (auto readerName : readers) { + // Filter eSE readers only + if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) { + std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader; + status = omapiSecureService()->getReader(readerName, &reader); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + mVSReaders[readerName] = reader; + } + } + } + } + + void TearDown() override { + if (mOmapiSeService != nullptr) { + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + reader->closeSessions(); + } + } + } + } + + bool isDebuggableBuild() { + char value[PROPERTY_VALUE_MAX] = {0}; + property_get("ro.system.build.type", value, ""); + if (strcmp(value, "userdebug") == 0) { + return true; + } + if (strcmp(value, "eng") == 0) { + return true; + } + return false; + } + + std::shared_ptr<aidl::android::se::omapi::ISecureElementService> omapiSecureService() { + return mOmapiSeService; + } + + static inline std::string const ESE_READER_PREFIX = "eSE"; + static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese"; + + std::vector<uint8_t> SELECTABLE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x31}; + std::vector<uint8_t> LONG_SELECT_RESPONSE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, + 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, + 0x43, 0x54, 0x53, 0x32}; + std::vector<uint8_t> NON_SELECTABLE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0xFF}; + + std::vector<std::vector<uint8_t>> ILLEGAL_COMMANDS_TRANSMIT = { + {0x00, 0x70, 0x00, 0x00}, + {0x00, 0x70, 0x80, 0x00}, + {0x00, 0xA4, 0x04, 0x04, 0x10, 0x4A, 0x53, 0x52, 0x31, 0x37, 0x37, + 0x54, 0x65, 0x73, 0x74, 0x65, 0x72, 0x20, 0x31, 0x2E, 0x30}}; + + /* OMAPI APDU Test case 1 and 3 */ + std::vector<std::vector<uint8_t>> NO_DATA_APDU = {{0x00, 0x06, 0x00, 0x00}, + {0x80, 0x06, 0x00, 0x00}, + {0xA0, 0x06, 0x00, 0x00}, + {0x94, 0x06, 0x00, 0x00}, + {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}}; + + /* OMAPI APDU Test case 2 and 4 */ + std::vector<std::vector<uint8_t>> DATA_APDU = {{0x00, 0x08, 0x00, 0x00, 0x00}, + {0x80, 0x08, 0x00, 0x00, 0x00}, + {0xA0, 0x08, 0x00, 0x00, 0x00}, + {0x94, 0x08, 0x00, 0x00, 0x00}, + {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}}; + + /* Case 2 APDU command expects the P2 received in the SELECT command as 1-byte outgoing data */ + std::vector<uint8_t> CHECK_SELECT_P2_APDU = {0x00, 0xF4, 0x00, 0x00, 0x00}; + + /* OMAPI APDU Test case 1 and 3 */ + std::vector<std::vector<uint8_t>> SW_62xx_NO_DATA_APDU = {{0x00, 0xF3, 0x00, 0x06}, + {0x00, 0xF3, 0x00, 0x0A, 0x01, 0xAA}}; + + /* OMAPI APDU Test case 2 and 4 */ + std::vector<uint8_t> SW_62xx_DATA_APDU = {0x00, 0xF3, 0x00, 0x08, 0x00}; + std::vector<uint8_t> SW_62xx_VALIDATE_DATA_APDU = {0x00, 0xF3, 0x00, 0x0C, 0x01, 0xAA, 0x00}; + std::vector<std::vector<uint8_t>> SW_62xx = { + {0x62, 0x00}, {0x62, 0x81}, {0x62, 0x82}, {0x62, 0x83}, {0x62, 0x85}, {0x62, 0xF1}, + {0x62, 0xF2}, {0x63, 0xF1}, {0x63, 0xF2}, {0x63, 0xC2}, {0x62, 0x02}, {0x62, 0x80}, + {0x62, 0x84}, {0x62, 0x86}, {0x63, 0x00}, {0x63, 0x81}}; + + std::vector<std::vector<uint8_t>> SEGMENTED_RESP_APDU = { + // Get response Case2 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xC2, 0x08, 0x00, 0x00}, + // Get response Case4 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xC4, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00}, + // Get response Case2 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xC6, 0x08, 0x00, 0x00}, + // Get response Case4 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xC8, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00}, + // Test device buffer capacity 7FFF data + {0x00, 0xC2, 0x7F, 0xFF, 0x00}, + // Get response 6CFF+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xCF, 0x08, 0x00, 0x00}, + // Get response with another CLA with answer length (P1P2) of 0x0800, 2048 bytes + {0x94, 0xC2, 0x08, 0x00, 0x00}}; + long SERVICE_CONNECTION_TIME_OUT = 3000; + + std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService; + + std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> + mVSReaders = {}; +}; + +/** Tests getReaders API */ +TEST_P(OMAPISEServiceHalTest, TestGetReaders) { + std::vector<std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> eseReaders = + {}; + + for (const auto& [name, reader] : mVSReaders) { + bool status = false; + LOG(INFO) << "Name of the reader: " << name; + + if (reader) { + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + } + ASSERT_TRUE(status); + + if (name.find(ESE_READER_PREFIX) == std::string::npos) { + LOG(ERROR) << "Incorrect Reader name"; + FAIL(); + } + + if (name.find(ESE_READER_PREFIX, 0) != std::string::npos) { + eseReaders.push_back(reader); + } else { + LOG(INFO) << "Reader not supported: " << name; + FAIL(); + } + } + + if (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())) { + ASSERT_GE(eseReaders.size(), 1); + } else { + ASSERT_TRUE(eseReaders.size() == 0); + } +} + +/** Tests OpenBasicChannel API when aid is null */ +TEST_P(OMAPISEServiceHalTest, TestOpenBasicChannelNullAid) { + ASSERT_TRUE(supportOMAPIReaders() == true); + std::vector<uint8_t> aid = {}; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + bool result = false; + + auto status = reader->openSession(&session); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + if (!session) { + LOG(ERROR) << "Could not open session"; + FAIL(); + } + + status = session->openBasicChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + if (channel != nullptr) { + status = channel->isBasicChannel(&result); + ASSERT_TRUE(status.isOk()) << "Basic Channel cannot be opened"; + } + } + } +} + +/** Tests OpenBasicChannel API when aid is provided */ +TEST_P(OMAPISEServiceHalTest, TestOpenBasicChannelNonNullAid) { + ASSERT_TRUE(supportOMAPIReaders() == true); + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + bool result = false; + + auto status = reader->openSession(&session); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + if (!session) { + LOG(ERROR) << "Could not open session"; + FAIL(); + } + + status = session->openBasicChannel(SELECTABLE_AID, 0x00, seListener, &channel); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + if (channel != nullptr) { + status = channel->isBasicChannel(&result); + ASSERT_TRUE(status.isOk()) << "Basic Channel cannot be opened"; + } + } + } +} + +/** Tests Select API */ +TEST_P(OMAPISEServiceHalTest, TestSelectableAid) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::vector<uint8_t> selectResponse = {}; + testSelectableAid(reader, SELECTABLE_AID, selectResponse); + } + } +} + +/** Tests Select API */ +TEST_P(OMAPISEServiceHalTest, TestLongSelectResponse) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::vector<uint8_t> selectResponse = {}; + testSelectableAid(reader, LONG_SELECT_RESPONSE_AID, selectResponse); + ASSERT_TRUE(verifyBerTlvData(selectResponse)) << "Select Response is not complete"; + } + } +} + +/** Test to fail open channel with wrong aid */ +TEST_P(OMAPISEServiceHalTest, TestWrongAid) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + testNonSelectableAid(reader, NON_SELECTABLE_AID); + } + } +} + +/** Tests with invalid cmds in Transmit */ +TEST_P(OMAPISEServiceHalTest, TestSecurityExceptionInTransmit) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + std::vector<uint8_t> selectResponse = {}; + + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(SELECTABLE_AID, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + + for (auto cmd : ILLEGAL_COMMANDS_TRANSMIT) { + std::vector<uint8_t> response = {}; + res = channel->transmit(cmd, &response); + ASSERT_EQ(res.getExceptionCode(), EX_SECURITY); + ASSERT_FALSE(res.isOk()) << "expected failed status for this test"; + } + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + } + } +} + +/** + * Tests Transmit API for all readers. + * + * Checks the return status and verifies the size of the + * response. + */ +TEST_P(OMAPISEServiceHalTest, TestTransmitApdu) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + for (auto apdu : NO_DATA_APDU) { + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + ASSERT_GE(response.size(), 2); + ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90)); + } + + for (auto apdu : DATA_APDU) { + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + /* 256 byte data and 2 bytes of status word */ + ASSERT_GE(response.size(), 258); + ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90)); + } + } + } +} + +/** + * Tests if underlying implementations returns the correct Status Word + * + * TO verify that : + * - the device does not modify the APDU sent to the Secure Element + * - the warning code is properly received by the application layer as SW answer + * - the verify that the application layer can fetch the additionnal data (when present) + */ +TEST_P(OMAPISEServiceHalTest, testStatusWordTransmit) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + for (auto apdu : SW_62xx_NO_DATA_APDU) { + for (uint8_t i = 0x00; i < SW_62xx.size(); i++) { + apdu[2] = i + 1; + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + std::vector<uint8_t> SW = SW_62xx[i]; + ASSERT_GE(response.size(), 2); + ASSERT_EQ(response[response.size() - 1], SW[1]); + ASSERT_EQ(response[response.size() - 2], SW[0]); + } + } + + for (uint8_t i = 0x00; i < SW_62xx.size(); i++) { + std::vector<uint8_t> apdu = SW_62xx_DATA_APDU; + apdu[2] = i + 1; + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + std::vector<uint8_t> SW = SW_62xx[i]; + ASSERT_GE(response.size(), 3); + ASSERT_EQ(response[response.size() - 1], SW[1]); + ASSERT_EQ(response[response.size() - 2], SW[0]); + } + + for (uint8_t i = 0x00; i < SW_62xx.size(); i++) { + std::vector<uint8_t> apdu = SW_62xx_VALIDATE_DATA_APDU; + apdu[2] = i + 1; + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + ASSERT_GE(response.size(), apdu.size() + 2); + std::vector<uint8_t> responseSubstring((response.begin() + 0), + (response.begin() + apdu.size())); + // We should not care about which channel number is actually assigned. + responseSubstring[0] = apdu[0]; + ASSERT_TRUE((responseSubstring == apdu)); + std::vector<uint8_t> SW = SW_62xx[i]; + ASSERT_EQ(response[response.size() - 1], SW[1]); + ASSERT_EQ(response[response.size() - 2], SW[0]); + } + } + } +} + +/** Test if the responses are segmented by the underlying implementation */ +TEST_P(OMAPISEServiceHalTest, TestSegmentedResponseTransmit) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + for (auto apdu : SEGMENTED_RESP_APDU) { + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + int expectedLength = (0x00 << 24) | (0x00 << 16) | (apdu[2] << 8) | apdu[3]; + ASSERT_EQ(response.size(), (expectedLength + 2)); + ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90)); + ASSERT_EQ((response[response.size() - 3] & 0xFF), (0xFF)); + } + } + } +} + +/** + * Tests the P2 value of the select command. + * + * Verifies that the default P2 value (0x00) is not modified by the underlying implementation. + */ +TEST_P(OMAPISEServiceHalTest, TestP2Value) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, CHECK_SELECT_P2_APDU, response); + ASSERT_GE(response.size(), 3); + ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90)); + ASSERT_EQ((response[response.size() - 3] & 0xFF), (0x00)); + } + } +} + +INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEServiceHalTest, + testing::ValuesIn(::android::getAidlHalInstanceNames( + aidl::android::se::omapi::ISecureElementService::descriptor)), + android::hardware::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEServiceHalTest); + +} // namespace diff --git a/omapi/java/Android.bp b/omapi/java/Android.bp new file mode 100644 index 000000000000..8d38da048d9b --- /dev/null +++ b/omapi/java/Android.bp @@ -0,0 +1,17 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "framework-omapi-sources", + srcs: [ + "**/*.java", + "**/*.aidl", + ], + visibility: ["//frameworks/base"], +} diff --git a/core/java/android/se/OWNERS b/omapi/java/android/se/OWNERS index 5682fd3281f4..5682fd3281f4 100644 --- a/core/java/android/se/OWNERS +++ b/omapi/java/android/se/OWNERS diff --git a/core/java/android/se/omapi/Channel.java b/omapi/java/android/se/omapi/Channel.java index 90ce11ae0313..90ce11ae0313 100644 --- a/core/java/android/se/omapi/Channel.java +++ b/omapi/java/android/se/omapi/Channel.java diff --git a/core/java/android/se/omapi/OWNERS b/omapi/java/android/se/omapi/OWNERS index 5682fd3281f4..5682fd3281f4 100644 --- a/core/java/android/se/omapi/OWNERS +++ b/omapi/java/android/se/omapi/OWNERS diff --git a/core/java/android/se/omapi/Reader.java b/omapi/java/android/se/omapi/Reader.java index 90c934d189fa..3c2135d9bc9d 100644 --- a/core/java/android/se/omapi/Reader.java +++ b/omapi/java/android/se/omapi/Reader.java @@ -170,7 +170,9 @@ public final class Reader { try { closeSessions(); return mReader.reset(); - } catch (RemoteException ignore) {return false;} + } catch (RemoteException ignore) { + return false; + } } } } diff --git a/core/java/android/se/omapi/SEService.java b/omapi/java/android/se/omapi/SEService.java index 333af91ac872..f42ca364b6d9 100644 --- a/core/java/android/se/omapi/SEService.java +++ b/omapi/java/android/se/omapi/SEService.java @@ -230,20 +230,20 @@ public final class SEService { * is not exist. * @return A Reader object for this uicc slot. */ - public @NonNull Reader getUiccReader(int slotNumber) { - if (slotNumber < 1) { - throw new IllegalArgumentException("slotNumber should be larger than 0"); - } - loadReaders(); + public @NonNull Reader getUiccReader(int slotNumber) { + if (slotNumber < 1) { + throw new IllegalArgumentException("slotNumber should be larger than 0"); + } + loadReaders(); - String readerName = UICC_TERMINAL + slotNumber; - Reader reader = mReaders.get(readerName); + String readerName = UICC_TERMINAL + slotNumber; + Reader reader = mReaders.get(readerName); - if (reader == null) { + if (reader == null) { throw new IllegalArgumentException("Reader:" + readerName + " doesn't exist"); - } + } - return reader; + return reader; } /** diff --git a/core/java/android/se/omapi/Session.java b/omapi/java/android/se/omapi/Session.java index d5f8c82bf47e..d5f8c82bf47e 100644 --- a/core/java/android/se/omapi/Session.java +++ b/omapi/java/android/se/omapi/Session.java diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 58d2185ea9e9..1df1bce014cb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -160,10 +160,12 @@ public class BluetoothEventManager { private void registerIntentReceiver(BroadcastReceiver receiver, IntentFilter filter) { if (mUserHandle == null) { // If userHandle has not been provided, simply call registerReceiver. - mContext.registerReceiver(receiver, filter, null, mReceiverHandler); + mContext.registerReceiver(receiver, filter, null, mReceiverHandler, + Context.RECEIVER_EXPORTED); } else { // userHandle was explicitly specified, so need to call multi-user aware API. - mContext.registerReceiverAsUser(receiver, mUserHandle, filter, null, mReceiverHandler); + mContext.registerReceiverAsUser(receiver, mUserHandle, filter, null, mReceiverHandler, + Context.RECEIVER_EXPORTED); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index a210e90a3cfc..8b17be1e8ec9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -19,7 +19,6 @@ package com.android.settingslib.notification; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AlertDialog; -import android.app.Dialog; import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; @@ -85,6 +84,7 @@ public class EnableZenModeDialog { @VisibleForTesting protected Context mContext; + private final int mThemeResId; @VisibleForTesting protected TextView mZenAlarmWarning; @VisibleForTesting @@ -97,10 +97,15 @@ public class EnableZenModeDialog { protected LayoutInflater mLayoutInflater; public EnableZenModeDialog(Context context) { + this(context, 0); + } + + public EnableZenModeDialog(Context context, int themeResId) { mContext = context; + mThemeResId = themeResId; } - public Dialog createDialog() { + public AlertDialog createDialog() { mNotificationManager = (NotificationManager) mContext. getSystemService(Context.NOTIFICATION_SERVICE); mForeverId = Condition.newId(mContext).appendPath("forever").build(); @@ -108,7 +113,7 @@ public class EnableZenModeDialog { mUserId = mContext.getUserId(); mAttached = false; - final AlertDialog.Builder builder = new AlertDialog.Builder(mContext) + final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId) .setTitle(R.string.zen_mode_settings_turn_on_dialog_title) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on, diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 0fe4efefc2cb..a944bf58d1f5 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -80,6 +80,7 @@ public class SystemSettings { Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Settings.System.RING_VIBRATION_INTENSITY, Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE Settings.System.DISPLAY_COLOR_MODE, Settings.System.ALARM_ALERT, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 462c3a5bba03..63acffba662f 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -120,6 +120,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); + VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.RINGTONE, URI_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR); VALIDATORS.put(System.ALARM_ALERT, URI_VALIDATOR); diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 3ccf5e4fbdd0..5fec4cccda6c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -53,8 +53,12 @@ open class GhostedViewLaunchAnimatorController( private val ghostedView: View, /** The [InteractionJankMonitor.CujType] associated to this animation. */ - private val cujType: Int? = null + private val cujType: Int? = null, + private var interactionJankMonitor: InteractionJankMonitor? = null ) : ActivityLaunchAnimator.Controller { + + constructor(view: View, type: Int) : this(view, type, null) + /** The container to which we will add the ghost view and expanding background. */ override var launchContainer = ghostedView.rootView as ViewGroup private val launchContainerOverlay: ViewGroupOverlay @@ -170,7 +174,7 @@ open class GhostedViewLaunchAnimatorController( val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX matrix.getValues(initialGhostViewMatrixValues) - cujType?.let { InteractionJankMonitor.getInstance().begin(ghostedView, it) } + cujType?.let { interactionJankMonitor?.begin(ghostedView, it) } } override fun onLaunchAnimationProgress( @@ -251,7 +255,7 @@ open class GhostedViewLaunchAnimatorController( return } - cujType?.let { InteractionJankMonitor.getInstance().end(it) } + cujType?.let { interactionJankMonitor?.end(it) } backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml index 0dec981bd4ae..991dc63ef830 100644 --- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml @@ -35,7 +35,7 @@ android:id="@+id/volume_number" android:layout_width="@dimen/tv_volume_dialog_bubble_size" android:layout_height="@dimen/tv_volume_dialog_bubble_size" - android:maxLength="2" + android:maxLength="3" android:gravity="center" android:fontFeatureSettings="tnum" android:background="@drawable/tv_volume_dialog_circle" diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index 86e2661f9534..98518c2bbf97 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -156,6 +156,14 @@ style="@style/InternetDialog.NetworkSummary"/> </LinearLayout> + <View + android:id="@+id/mobile_toggle_divider" + android:layout_width="1dp" + android:layout_height="28dp" + android:layout_marginEnd="16dp" + android:layout_gravity="center_vertical" + android:background="?android:attr/textColorSecondary"/> + <FrameLayout android:layout_width="@dimen/settingslib_switch_track_width" android:layout_height="48dp" @@ -367,8 +375,9 @@ android:id="@+id/done_layout" android:layout_width="67dp" android:layout_height="48dp" + android:layout_marginTop="8dp" android:layout_marginEnd="24dp" - android:layout_marginBottom="40dp" + android:layout_marginBottom="34dp" android:layout_gravity="end|center_vertical" android:clickable="true" android:focusable="true"> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 07e28b6d7f20..cb34218fd2fb 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -53,13 +53,17 @@ <style name="TextAppearance.InternetDialog.Active"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textSize">16sp</item> - <item name="android:textColor">@color/connected_network_primary_color</item> + <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> <item name="android:textDirection">locale</item> </style> <style name="TextAppearance.InternetDialog.Secondary.Active"> <item name="android:textSize">14sp</item> - <item name="android:textColor">@color/connected_network_secondary_color</item> + <item name="android:textColor">?android:attr/textColorSecondaryInverse</item> + </style> + + <style name="InternetDialog.Divider.Active"> + <item name="android:background">?android:attr/textColorSecondaryInverse</item> </style> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index d972b7fc25a6..dd67961343fd 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -957,4 +957,10 @@ <style name="TextAppearance.InternetDialog.Secondary.Active"/> + <style name="InternetDialog.Divider"> + <item name="android:background">?android:attr/textColorSecondary</item> + </style> + + <style name="InternetDialog.Divider.Active"/> + </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 8bd0f910dac3..01497516e0b1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -95,4 +95,9 @@ oneway interface IOverviewProxy { * Sent when screen turned on and ready to use (blocker scrim is hidden) */ void onScreenTurnedOn() = 21; + + /** + * Sent when the desired dark intensity of the nav buttons has changed + */ + void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 6154d84d5b37..8d98a7540a05 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -18,7 +18,6 @@ package com.android.systemui.shared.recents.utilities; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; import android.annotation.TargetApi; import android.content.Context; @@ -126,10 +125,9 @@ public class Utilities { final WindowManager windowManager = context.getSystemService(WindowManager.class); final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); - float originalSmallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()), + float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()), context.getResources().getConfiguration().densityDpi); - return dpiFromPx(Math.min(bounds.width(), bounds.height()), DENSITY_DEVICE_STABLE) - >= TABLET_MIN_DPS && originalSmallestWidth >= TABLET_MIN_DPS; + return smallestWidth >= TABLET_MIN_DPS; } public static float dpiFromPx(float size, int densityDpi) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index e24f07c21076..b56d189d3ab3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -801,13 +801,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT); } + boolean lockedOutStateChanged = false; if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged |= !mFingerprintLockedOutPermanent; mFingerprintLockedOutPermanent = true; requireStrongAuthIfAllLockedOut(); } if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged |= !mFingerprintLockedOut; mFingerprintLockedOut = true; if (isUdfpsEnrolled()) { updateFingerprintListeningState(); @@ -820,9 +823,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT); } } + + if (lockedOutStateChanged) { + notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT); + } } private void handleFingerprintLockoutReset() { + boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent; mFingerprintLockedOut = false; mFingerprintLockedOutPermanent = false; @@ -837,6 +845,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else { updateFingerprintListeningState(); } + + if (changed) { + notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT); + } } private void setFingerprintRunningState(int fingerprintRunningState) { @@ -999,7 +1011,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + boolean lockedOutStateChanged = false; if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged = !mFaceLockedOutPermanent; mFaceLockedOutPermanent = true; requireStrongAuthIfAllLockedOut(); } @@ -1011,11 +1025,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab BiometricSourceType.FACE); } } + + if (lockedOutStateChanged) { + notifyLockedOutStateChanged(BiometricSourceType.FACE); + } } private void handleFaceLockoutReset() { + boolean changed = mFaceLockedOutPermanent; mFaceLockedOutPermanent = false; + updateFaceListeningState(); + + if (changed) { + notifyLockedOutStateChanged(BiometricSourceType.FACE); + } } private void setFaceRunningState(int faceRunningState) { @@ -1237,6 +1261,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + private void notifyLockedOutStateChanged(BiometricSourceType type) { + Assert.isMainThread(); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onLockedOutStateChanged(type); + } + } + } + public boolean isScreenOn() { return mScreenOn; } @@ -2454,6 +2488,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + public boolean isFingerprintLockedOut() { + return mFingerprintLockedOut || mFingerprintLockedOutPermanent; + } + /** * If biometrics hardware is available, not disabled, and user has enrolled templates. * This does NOT check if the device is encrypted or in lockdown. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 12431984c9b9..8170a81a09e6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -292,6 +292,11 @@ public class KeyguardUpdateMonitorCallback { public void onStrongAuthStateChanged(int userId) { } /** + * When the current user's locked out state changed. + */ + public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) { } + + /** * Called when the dream's window state is changed. * @param dreaming true if the dream's window has been created and is visible */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 223eb78044c4..8f4d6f6aa973 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -255,7 +255,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud private void maybeShowInputBouncer() { if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) { mKeyguardViewManager.showBouncer(true); - mKeyguardViewManager.resetAlternateAuth(false); } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java index 99c311e37217..53586f58c5d2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java @@ -23,6 +23,8 @@ import com.android.systemui.util.ViewController; import com.google.common.util.concurrent.ListenableFuture; +import java.util.Optional; + /** * {@link CommunalSource} defines an interface for working with a source for communal data. Clients * may request a communal surface that can be shown within a {@link android.view.SurfaceView}. @@ -30,6 +32,28 @@ import com.google.common.util.concurrent.ListenableFuture; */ public interface CommunalSource { /** + * {@link Connector} defines an interface for {@link CommunalSource} instances to be generated. + */ + interface Connector { + ListenableFuture<Optional<CommunalSource>> connect(); + } + + /** + * The {@link Observer} interface specifies an entity which {@link CommunalSource} listeners + * can be informed of changes to the source, which will require updating. Note that this deals + * with changes to the source itself, not content which will be updated through the + * {@link CommunalSource} interface. + */ + interface Observer { + interface Callback { + void onSourceChanged(); + } + + void addCallback(Callback callback); + void removeCallback(Callback callback); + } + + /** * {@link CommunalViewResult} is handed back from {@link #requestCommunalView(Context)} and * contains the view to be displayed and its associated controller. */ diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java new file mode 100644 index 000000000000..3c2b79e25467 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java @@ -0,0 +1,161 @@ +/* + * 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 com.android.systemui.communal; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; + +import com.android.systemui.CoreStartable; +import com.android.systemui.R; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.Optional; + +import javax.inject.Inject; + +/** + * The {@link CommunalSourcePrimer} is responsible for priming SystemUI with a pre-configured + * Communal source. The SystemUI service binds to the component to retrieve the + * {@link CommunalSource}. {@link CommunalSourcePrimer} has no effect + * if there is no pre-defined value. + */ +@SysUISingleton +public class CommunalSourcePrimer extends CoreStartable { + private static final String TAG = "CommunalSourcePrimer"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final DelayableExecutor mMainExecutor; + private final CommunalSourceMonitor mMonitor; + private final int mBaseReconnectDelayMs; + private final int mMaxReconnectAttempts; + + private int mReconnectAttempts = 0; + private Runnable mCurrentReconnectCancelable; + private ListenableFuture<Optional<CommunalSource>> mGetSourceFuture; + + private final Optional<CommunalSource.Connector> mConnector; + private final Optional<CommunalSource.Observer> mObserver; + + private final Runnable mConnectRunnable = new Runnable() { + @Override + public void run() { + mCurrentReconnectCancelable = null; + connect(); + } + }; + + @Inject + public CommunalSourcePrimer(Context context, @Main Resources resources, + DelayableExecutor mainExecutor, + CommunalSourceMonitor monitor, + Optional<CommunalSource.Connector> connector, + Optional<CommunalSource.Observer> observer) { + super(context); + mMainExecutor = mainExecutor; + mMonitor = monitor; + mConnector = connector; + mObserver = observer; + + mMaxReconnectAttempts = resources.getInteger( + R.integer.config_communalSourceMaxReconnectAttempts); + mBaseReconnectDelayMs = resources.getInteger( + R.integer.config_communalSourceReconnectBaseDelay); + } + + @Override + public void start() { + } + + private void initiateConnectionAttempt() { + // Reset attempts + mReconnectAttempts = 0; + mMonitor.setSource(null); + + // The first attempt is always a direct invocation rather than delayed. + connect(); + } + + private void scheduleConnectionAttempt() { + // always clear cancelable if present. + if (mCurrentReconnectCancelable != null) { + mCurrentReconnectCancelable.run(); + mCurrentReconnectCancelable = null; + } + + if (mReconnectAttempts >= mMaxReconnectAttempts) { + if (DEBUG) { + Log.d(TAG, "exceeded max connection attempts."); + } + return; + } + + final long reconnectDelayMs = + (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts); + + if (DEBUG) { + Log.d(TAG, + "scheduling connection attempt in " + reconnectDelayMs + "milliseconds"); + } + + mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable, + reconnectDelayMs); + + mReconnectAttempts++; + } + + @Override + protected void onBootCompleted() { + if (mObserver.isPresent()) { + mObserver.get().addCallback(() -> initiateConnectionAttempt()); + } + initiateConnectionAttempt(); + } + + private void connect() { + if (DEBUG) { + Log.d(TAG, "attempting to communal to communal source"); + } + + if (mGetSourceFuture != null) { + if (DEBUG) { + Log.d(TAG, "canceling in-flight connection"); + } + mGetSourceFuture.cancel(true); + } + + mGetSourceFuture = mConnector.get().connect(); + mGetSourceFuture.addListener(() -> { + try { + Optional<CommunalSource> result = mGetSourceFuture.get(); + if (result.isPresent()) { + final CommunalSource source = result.get(); + source.addCallback(() -> initiateConnectionAttempt()); + mMonitor.setSource(source); + } else { + scheduleConnectionAttempt(); + } + } catch (Exception e) { + e.printStackTrace(); + } + }, mMainExecutor); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e97e7622c272..fbc9ba605000 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -793,7 +793,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; } else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; - } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) { + } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 + || mUpdateMonitor.isFingerprintLockedOut())) { return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) { return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; @@ -820,6 +821,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private final KeyguardStateController mKeyguardStateController; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; + private final InteractionJankMonitor mInteractionJankMonitor; private boolean mWallpaperSupportsAmbientMode; /** @@ -845,7 +847,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, - Lazy<NotificationShadeDepthController> notificationShadeDepthController) { + Lazy<NotificationShadeDepthController> notificationShadeDepthController, + InteractionJankMonitor interactionJankMonitor) { super(context); mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; @@ -882,6 +885,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mKeyguardStateController = keyguardStateController; mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; + mInteractionJankMonitor = interactionJankMonitor; } public void userActivity() { @@ -2245,8 +2249,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, onKeyguardExitFinished(); mKeyguardViewControllerLazy.get().hide(0 /* startTime */, 0 /* fadeoutDuration */); - InteractionJankMonitor.getInstance() - .end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); } @Override @@ -2255,7 +2258,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } }; try { - InteractionJankMonitor.getInstance().begin( + mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RunRemoteAnimation")); runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps, wallpapers, nonApps, callback); @@ -2271,14 +2274,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback; mSurfaceBehindRemoteAnimationRunning = true; - InteractionJankMonitor.getInstance().begin( + mInteractionJankMonitor.begin( createInteractionJankMonitorConf("DismissPanel")); // Pass the surface and metadata to the unlock animation controller. mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation( apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); } else { - InteractionJankMonitor.getInstance().begin( + mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RemoteAnimationDisabled")); mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration); @@ -2288,7 +2291,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, // supported, so it's always null. mContext.getMainExecutor().execute(() -> { if (finishedCallback == null) { - InteractionJankMonitor.getInstance().end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); return; } @@ -2316,8 +2319,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } catch (RemoteException e) { Slog.e(TAG, "RemoteException"); } finally { - InteractionJankMonitor.getInstance() - .end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); } } @@ -2328,8 +2330,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } catch (RemoteException e) { Slog.e(TAG, "RemoteException"); } finally { - InteractionJankMonitor.getInstance() - .cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); + mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); } } }); 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..8d23e9f6a12b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.PowerManager; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; @@ -97,7 +98,8 @@ public class KeyguardModule { KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, - Lazy<NotificationShadeDepthController> notificationShadeDepthController) { + Lazy<NotificationShadeDepthController> notificationShadeDepthController, + InteractionJankMonitor interactionJankMonitor) { return new KeyguardViewMediator( context, falsingCollector, @@ -120,7 +122,8 @@ public class KeyguardModule { keyguardStateController, keyguardUnlockAnimationController, unlockedScreenOffAnimationController, - notificationShadeDepthController + notificationShadeDepthController, + interactionJankMonitor ); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index e87558ebee27..47ef5e4c62fd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -833,12 +833,15 @@ internal object MediaPlayerData { ) private val comparator = - compareByDescending<MediaSortKey> { it.data.isPlaying == true && it.data.isLocalSession } - .thenByDescending { it.data.isPlaying } - .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec } - .thenByDescending { !it.data.resumption } - .thenByDescending { it.updateTime } - .thenByDescending { !it.data.isLocalSession } + compareByDescending<MediaSortKey> { it.data.isPlaying == true && + it.data.playbackLocation == MediaData.PLAYBACK_LOCAL } + .thenByDescending { it.data.isPlaying == true && + it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL } + .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec } + .thenByDescending { !it.data.resumption } + .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE } + .thenByDescending { it.updateTime } + .thenByDescending { it.data.notificationKey } private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator) private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf() diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 6f0c88710461..bda07f428218 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -82,9 +82,9 @@ data class MediaData( */ var resumeAction: Runnable?, /** - * Local or remote playback + * Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE */ - var isLocalSession: Boolean = true, + var playbackLocation: Int = PLAYBACK_LOCAL, /** * Indicates that this player is a resumption player (ie. It only shows a play actions which * will start the app and start playing). @@ -110,7 +110,20 @@ data class MediaData( * Timestamp when this player was last active. */ var lastActive: Long = 0L -) +) { + companion object { + /** Media is playing on the local device */ + const val PLAYBACK_LOCAL = 0 + /** Media is cast but originated on the local device */ + const val PLAYBACK_CAST_LOCAL = 1 + /** Media is from a remote cast notification */ + const val PLAYBACK_CAST_REMOTE = 2 + } + + fun isLocalSession(): Boolean { + return playbackLocation == PLAYBACK_LOCAL + } +} /** State of a media action. */ data class MediaAction( diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index c9b6d9c50e7a..6e86bef40181 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -27,6 +27,8 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.ImageDecoder @@ -145,6 +147,24 @@ class MediaDataManager( private var smartspaceSession: SmartspaceSession? = null private var allowMediaRecommendations = Utils.allowMediaRecommendations(context) + /** + * Check whether this notification is an RCN + * TODO(b/204910409) implement new API for explicitly declaring this + */ + private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean { + val pm = context.packageManager + try { + val info = pm.getApplicationInfo(sbn.packageName, PackageManager.MATCH_SYSTEM_ONLY) + if (info.privateFlags and ApplicationInfo.PRIVATE_FLAG_PRIVILEGED != 0) { + val extras = sbn.notification.extras + if (extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) { + return true + } + } + } catch (e: PackageManager.NameNotFoundException) { } + return false + } + @Inject constructor( context: Context, @@ -434,7 +454,7 @@ class MediaDataManager( val existed = mediaEntries[key] != null backgroundExecutor.execute { mediaEntries[key]?.let { mediaData -> - if (mediaData.isLocalSession) { + if (mediaData.isLocalSession()) { mediaData.token?.let { val mediaController = mediaControllerFactory.create(it) mediaController.transportControls.stop() @@ -618,8 +638,11 @@ class MediaDataManager( } } - val isLocalSession = mediaController.playbackInfo?.playbackType == - MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL + val playbackLocation = + if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE + else if (mediaController.playbackInfo?.playbackType == + MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL + else MediaData.PLAYBACK_CAST_LOCAL val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null val lastActive = systemClock.elapsedRealtime() foregroundExecutor.execute { @@ -629,7 +652,7 @@ class MediaDataManager( onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app, smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null, - active, resumeAction = resumeAction, isLocalSession = isLocalSession, + active, resumeAction = resumeAction, playbackLocation = playbackLocation, notificationKey = key, hasCheckedForResume = hasCheckedForResume, isPlaying = isPlaying, isClearable = sbn.isClearable(), lastActive = lastActive)) @@ -754,7 +777,7 @@ class MediaDataManager( fun onNotificationRemoved(key: String) { Assert.isMainThread() val removed = mediaEntries.remove(key) - if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession) { + if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession()) { Log.d(TAG, "Not removing $key because resumable") // Move to resume key (aka package name) if that key doesn't already exist. val resumeAction = getResumeMediaAction(removed.resumeAction!!) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index 608c784f5d39..d8095f3179b3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -193,7 +193,7 @@ class MediaResumeListener @Inject constructor( mediaBrowser = null } // If we don't have a resume action, check if we haven't already - if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession) { + if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession()) { // TODO also check for a media button receiver intended for restarting (b/154127084) Log.d(TAG, "Checking for service component for " + data.packageName) val pm = context.packageManager diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java new file mode 100644 index 000000000000..52103d3bd739 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -0,0 +1,246 @@ +/* + * 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 com.android.systemui.navigationbar; + +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; + +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.view.accessibility.AccessibilityManager; + +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.assist.AssistManager; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import dagger.Lazy; + +/** + * Extracts shared elements between navbar and taskbar delegate to de-dupe logic and help them + * experience the joys of friendship. + * The events are then passed through + * + * Currently consolidates + * * A11y + * * Assistant + */ +@SysUISingleton +public final class NavBarHelper implements + AccessibilityButtonModeObserver.ModeChangedListener, + OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, + Dumpable { + private final AccessibilityManager mAccessibilityManager; + private final Lazy<AssistManager> mAssistManagerLazy; + private final UserTracker mUserTracker; + private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>(); + private Context mContext; + private ContentResolver mContentResolver; + private boolean mAssistantAvailable; + private boolean mLongPressHomeEnabled; + private boolean mAssistantTouchGestureEnabled; + private int mNavBarMode; + + private final ContentObserver mAssistContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updateAssitantAvailability(); + } + }; + + @Inject + public NavBarHelper(AccessibilityManager accessibilityManager, + AccessibilityManagerWrapper accessibilityManagerWrapper, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, + OverviewProxyService overviewProxyService, + Lazy<AssistManager> assistManagerLazy, + NavigationModeController navigationModeController, + UserTracker userTracker, + DumpManager dumpManager) { + mAccessibilityManager = accessibilityManager; + mAssistManagerLazy = assistManagerLazy; + mUserTracker = userTracker; + accessibilityManagerWrapper.addCallback( + accessibilityManager1 -> NavBarHelper.this.dispatchA11yEventUpdate()); + mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; + + mAccessibilityButtonModeObserver.addListener(this); + mNavBarMode = navigationModeController.addListener(this); + overviewProxyService.addCallback(this); + dumpManager.registerDumpable(this); + } + + public void init(Context context) { + mContext = context; + mContentResolver = mContext.getContentResolver(); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), + false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + updateAssitantAvailability(); + } + + public void destroy() { + mContentResolver.unregisterContentObserver(mAssistContentObserver); + } + + /** + * @param listener Will immediately get callbacks based on current state + */ + public void registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) { + mA11yEventListeners.add(listener); + listener.updateAccessibilityServicesState(); + listener.updateAssistantAvailable(mAssistantAvailable); + } + + public void removeNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) { + mA11yEventListeners.remove(listener); + } + + private void dispatchA11yEventUpdate() { + for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) { + listener.updateAccessibilityServicesState(); + } + } + + private void dispatchAssistantEventUpdate(boolean assistantAvailable) { + for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) { + listener.updateAssistantAvailable(assistantAvailable); + } + } + + @Override + public void onAccessibilityButtonModeChanged(int mode) { + dispatchA11yEventUpdate(); + } + + /** + * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and + * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE} + * + * @return the a11y button clickable and long_clickable states, or 0 if there is no + * a11y button in the navbar + */ + public int getA11yButtonState() { + // AccessibilityManagerService resolves services for the current user since the local + // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission + final List<String> a11yButtonTargets = + mAccessibilityManager.getAccessibilityShortcutTargets( + AccessibilityManager.ACCESSIBILITY_BUTTON); + final int requestingServices = a11yButtonTargets.size(); + + // If accessibility button is floating menu mode, click and long click state should be + // disabled. + if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode() + == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { + return 0; + } + + return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) + | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); + } + + @Override + public void onConnectionChanged(boolean isConnected) { + if (isConnected) { + updateAssitantAvailability(); + } + } + + private void updateAssitantAvailability() { + boolean assistantAvailableForUser = mAssistManagerLazy.get() + .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; + boolean longPressDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); + mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, + mUserTracker.getUserId()) != 0; + boolean gestureDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_assistTouchGestureEnabledDefault); + mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0, + mUserTracker.getUserId()) != 0; + + mAssistantAvailable = assistantAvailableForUser + && mAssistantTouchGestureEnabled + && QuickStepContract.isGesturalMode(mNavBarMode); + dispatchAssistantEventUpdate(mAssistantAvailable); + } + + public boolean getLongPressHomeEnabled() { + return mLongPressHomeEnabled; + } + + @Override + public void startAssistant(Bundle bundle) { + mAssistManagerLazy.get().startAssist(bundle); + } + + @Override + public void onNavigationModeChanged(int mode) { + mNavBarMode = mode; + updateAssitantAvailability(); + } + + /** + * Callbacks will get fired once immediately after registering via + * {@link #registerNavTaskStateUpdater(NavbarTaskbarStateUpdater)} + */ + public interface NavbarTaskbarStateUpdater { + void updateAccessibilityServicesState(); + void updateAssistantAvailable(boolean available); + } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("NavbarTaskbarFriendster"); + pw.println(" longPressHomeEnabled=" + mLongPressHomeEnabled); + pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled); + pw.println(" mAssistantAvailable=" + mAssistantAvailable); + pw.println(" mNavBarMode=" + mNavBarMode); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index bdacc4118442..e0da9a0dca2c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -67,22 +67,18 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; -import android.database.ContentObserver; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.inputmethodservice.InputMethodService; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; -import android.provider.Settings; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.Log; @@ -200,8 +196,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final Handler mHandler; private final NavigationBarOverlayController mNavbarOverlayController; private final UiEventLogger mUiEventLogger; - private final NavigationBarA11yHelper mNavigationBarA11yHelper; - private final UserTracker mUserTracker; + private final NavBarHelper mNavBarHelper; private final NotificationShadeDepthController mNotificationShadeDepthController; private Bundle mSavedState; @@ -213,9 +208,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private int mNavigationIconHints = 0; private @TransitionMode int mNavigationBarMode; private ContentResolver mContentResolver; - private boolean mAssistantAvailable; private boolean mLongPressHomeEnabled; - private boolean mAssistantTouchGestureEnabled; private int mDisabledFlags1; private int mDisabledFlags2; @@ -309,16 +302,31 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } }; + private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = + new NavBarHelper.NavbarTaskbarStateUpdater() { + @Override + public void updateAccessibilityServicesState() { + updateAcessibilityStateFlags(); + } + + @Override + public void updateAssistantAvailable(boolean available) { + // TODO(b/198002034): Content observers currently can still be called back after + // being unregistered, and in this case we can ignore the change if the nav bar + // has been destroyed already + if (mNavigationBarView == null) { + return; + } + mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); + updateAssistantEntrypoints(available); + } + }; + private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { @Override public void onConnectionChanged(boolean isConnected) { mNavigationBarView.updateStates(); updateScreenPinningGestures(); - - // Send the assistant availability upon connection - if (isConnected) { - updateAssistantEntrypoints(); - } } @Override @@ -421,20 +429,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } }; - private final ContentObserver mAssistContentObserver = new ContentObserver( - new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange, Uri uri) { - // TODO(b/198002034): Content observers currently can still be called back after being - // unregistered, and in this case we can ignore the change if the nav bar has been - // destroyed already - if (mNavigationBarView == null) { - return; - } - updateAssistantEntrypoints(); - } - }; - private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -504,7 +498,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Main Handler mainHandler, NavigationBarOverlayController navbarOverlayController, UiEventLogger uiEventLogger, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, UserTracker userTracker, LightBarController mainLightBarController, LightBarController.Factory lightBarControllerFactory, @@ -535,8 +529,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; mUiEventLogger = uiEventLogger; - mNavigationBarA11yHelper = navigationBarA11yHelper; - mUserTracker = userTracker; + mNavBarHelper = navBarHelper; mNotificationShadeDepthController = notificationShadeDepthController; mMainLightBarController = mainLightBarController; mLightBarControllerFactory = lightBarControllerFactory; @@ -568,18 +561,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; mCommandQueue.addCallback(this); - mAssistantAvailable = mAssistManagerLazy.get() - .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; + mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); mContentResolver = mContext.getContentResolver(); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), - false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), - false, mAssistContentObserver, UserHandle.USER_ALL); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), - false, mAssistContentObserver, UserHandle.USER_ALL); + mNavBarHelper.init(mContext); mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean( R.bool.allow_force_nav_bar_handle_opaque); mForceNavBarHandleOpaque = DeviceConfig.getBoolean( @@ -593,7 +577,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, )).filter(duration -> duration != 0); DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener); - updateAssistantEntrypoints(); if (savedState != null) { mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0); @@ -620,8 +603,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mWindowManager.removeViewImmediate(mNavigationBarView.getRootView()); mNavigationModeController.removeListener(this); - mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener); - mContentResolver.unregisterContentObserver(mAssistContentObserver); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.destroy(); mDeviceProvisionedController.removeCallback(mUserSetupListener); mNotificationShadeDepthController.removeListener(mDepthListener); @@ -643,7 +626,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); mNavigationBarView.setBehavior(mBehavior); - mNavigationBarA11yHelper.registerA11yEventListener(mAccessibilityListener); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener); mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener); @@ -716,7 +699,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mHandler.removeCallbacks(mAutoDim); mHandler.removeCallbacks(mOnVariableDurationHomeLongClick); mHandler.removeCallbacks(mEnableLayoutTransitions); - mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mFrame = null; mNavigationBarView = null; mOrientationHandle = null; @@ -885,7 +868,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, pw.println(" mCurrentRotation=" + mCurrentRotation); pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs); pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled); - pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled); pw.println(" mNavigationBarWindowState=" + windowStateToString(mNavigationBarWindowState)); pw.println(" mNavigationBarMode=" @@ -1175,7 +1157,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); - updateAccessibilityServicesState(); + updateAcessibilityStateFlags(); ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton(); imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick); @@ -1400,8 +1382,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, return true; } - void updateAccessibilityServicesState() { - int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + void updateAcessibilityStateFlags() { + int a11yFlags = mNavBarHelper.getA11yButtonState(); if (mNavigationBarView != null) { boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; @@ -1413,7 +1395,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, public void updateSystemUiStateFlags(int a11yFlags) { if (a11yFlags < 0) { - a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + a11yFlags = mNavBarHelper.getA11yButtonState(); } boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; @@ -1440,24 +1422,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } } - private void updateAssistantEntrypoints() { - mAssistantAvailable = mAssistManagerLazy.get() - .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; - boolean longPressDefault = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); - mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, - mUserTracker.getUserId()) != 0; - boolean gestureDefault = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_assistTouchGestureEnabledDefault); - mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0, - mUserTracker.getUserId()) != 0; + private void updateAssistantEntrypoints(boolean assistantAvailable) { if (mOverviewProxyService.getProxy() != null) { try { - mOverviewProxyService.getProxy().onAssistantAvailable(mAssistantAvailable - && mAssistantTouchGestureEnabled - && QuickStepContract.isGesturalMode(mNavBarMode)); + mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable); } catch (RemoteException e) { Log.w(TAG, "Unable to send assistant availability data to launcher"); } @@ -1528,8 +1496,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onNavigationModeChanged(int mode) { mNavBarMode = mode; - // update assistant entry points on system navigation radio button click - updateAssistantEntrypoints(); if (!QuickStepContract.isGesturalMode(mode)) { // Reset the override alpha @@ -1568,9 +1534,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavigationBarView.getBarTransitions().finishAnimations(); } - private final NavigationBarA11yHelper.NavA11yEventListener mAccessibilityListener = - this::updateAccessibilityServicesState; - private WindowManager.LayoutParams getBarLayoutParams(int rotation) { WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation); lp.paramsForRotation = new WindowManager.LayoutParams[4]; @@ -1674,7 +1637,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user - updateAccessibilityServicesState(); + updateAcessibilityStateFlags(); } } }; @@ -1710,7 +1673,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final Handler mMainHandler; private final NavigationBarOverlayController mNavbarOverlayController; private final UiEventLogger mUiEventLogger; - private final NavigationBarA11yHelper mNavigationBarA11yHelper; + private final NavBarHelper mNavBarHelper; private final UserTracker mUserTracker; private final LightBarController mMainLightBarController; private final LightBarController.Factory mLightBarControllerFactory; @@ -1743,7 +1706,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Main Handler mainHandler, NavigationBarOverlayController navbarOverlayController, UiEventLogger uiEventLogger, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, UserTracker userTracker, LightBarController mainLightBarController, LightBarController.Factory lightBarControllerFactory, @@ -1773,7 +1736,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mMainHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; mUiEventLogger = uiEventLogger; - mNavigationBarA11yHelper = navigationBarA11yHelper; + mNavBarHelper = navBarHelper; mUserTracker = userTracker; mMainLightBarController = mainLightBarController; mLightBarControllerFactory = lightBarControllerFactory; @@ -1794,7 +1757,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mSplitScreenOptional, mRecentsOptional, mStatusBarOptionalLazy, mShadeController, mNotificationRemoteInputManager, mNotificationShadeDepthController, mSystemActions, mMainHandler, - mNavbarOverlayController, mUiEventLogger, mNavigationBarA11yHelper, + mNavbarOverlayController, mUiEventLogger, mNavBarHelper, mUserTracker, mMainLightBarController, mLightBarControllerFactory, mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional, mInputMethodManager); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java deleted file mode 100644 index 13e6d8b410d6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.android.systemui.navigationbar; - -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; - -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; - -import android.view.accessibility.AccessibilityManager; - -import com.android.systemui.accessibility.AccessibilityButtonModeObserver; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -/** - * Extracts shared elements of a11y necessary between navbar and taskbar delegate - */ -@SysUISingleton -public final class NavigationBarA11yHelper implements - AccessibilityButtonModeObserver.ModeChangedListener { - private final AccessibilityManager mAccessibilityManager; - private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; - private final List<NavA11yEventListener> mA11yEventListeners = new ArrayList<>(); - - @Inject - public NavigationBarA11yHelper(AccessibilityManager accessibilityManager, - AccessibilityManagerWrapper accessibilityManagerWrapper, - AccessibilityButtonModeObserver accessibilityButtonModeObserver) { - mAccessibilityManager = accessibilityManager; - accessibilityManagerWrapper.addCallback( - accessibilityManager1 -> NavigationBarA11yHelper.this.dispatchEventUpdate()); - mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; - - mAccessibilityButtonModeObserver.addListener(this); - } - - public void registerA11yEventListener(NavA11yEventListener listener) { - mA11yEventListeners.add(listener); - } - - public void removeA11yEventListener(NavA11yEventListener listener) { - mA11yEventListeners.remove(listener); - } - - private void dispatchEventUpdate() { - for (NavA11yEventListener listener : mA11yEventListeners) { - listener.updateAccessibilityServicesState(); - } - } - - @Override - public void onAccessibilityButtonModeChanged(int mode) { - dispatchEventUpdate(); - } - - /** - * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and - * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE} - * - * @return the a11y button clickable and long_clickable states, or 0 if there is no - * a11y button in the navbar - */ - public int getA11yButtonState() { - // AccessibilityManagerService resolves services for the current user since the local - // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission - final List<String> a11yButtonTargets = - mAccessibilityManager.getAccessibilityShortcutTargets( - AccessibilityManager.ACCESSIBILITY_BUTTON); - final int requestingServices = a11yButtonTargets.size(); - - // If accessibility button is floating menu mode, click and long click state should be - // disabled. - if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode() - == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { - return 0; - } - - return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) - | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); - } - - public interface NavA11yEventListener { - void updateAccessibilityServicesState(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 3dc79c43d894..0429c022234d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import java.io.FileDescriptor; @@ -100,11 +101,12 @@ public class NavigationBarController implements CommandQueue commandQueue, @Main Handler mainHandler, ConfigurationController configurationController, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, NavigationBar.Factory navigationBarFactory, DumpManager dumpManager, - AutoHideController autoHideController) { + AutoHideController autoHideController, + LightBarController lightBarController) { mContext = context; mHandler = mainHandler; mNavigationBarFactory = navigationBarFactory; @@ -115,8 +117,8 @@ public class NavigationBarController implements mNavMode = navigationModeController.addListener(this); mTaskbarDelegate = taskbarDelegate; mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, - navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer, - dumpManager, autoHideController); + navBarHelper, navigationModeController, sysUiFlagsContainer, + dumpManager, autoHideController, lightBarController); mIsTablet = isTablet(mContext); dumpManager.registerDumpable(this); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index d707dbdf5cba..3d58a5a81251 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -43,6 +43,8 @@ import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; import android.view.Display; import android.view.InsetsVisibilities; import android.view.View; @@ -62,6 +64,9 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; +import com.android.systemui.statusbar.phone.BarTransitions; +import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.LightBarTransitionsController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -73,19 +78,32 @@ import javax.inject.Singleton; public class TaskbarDelegate implements CommandQueue.Callbacks, OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, ComponentCallbacks, Dumpable { + private static final String TAG = TaskbarDelegate.class.getSimpleName(); private final EdgeBackGestureHandler mEdgeBackGestureHandler; - + private boolean mInitialized; private CommandQueue mCommandQueue; private OverviewProxyService mOverviewProxyService; - private NavigationBarA11yHelper mNavigationBarA11yHelper; + private NavBarHelper mNavBarHelper; private NavigationModeController mNavigationModeController; private SysUiState mSysUiState; private AutoHideController mAutoHideController; + private LightBarController mLightBarController; + private LightBarTransitionsController mLightBarTransitionsController; private int mDisplayId; private int mNavigationIconHints; - private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener = - this::updateSysuiFlags; + private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = + new NavBarHelper.NavbarTaskbarStateUpdater() { + @Override + public void updateAccessibilityServicesState() { + updateSysuiFlags(); + } + + @Override + public void updateAssistantAvailable(boolean available) { + updateAssistantAvailability(available); + } + }; private int mDisabledFlags; private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING; private @Behavior int mBehavior; @@ -125,27 +143,56 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, public void setDependencies(CommandQueue commandQueue, OverviewProxyService overviewProxyService, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, NavigationModeController navigationModeController, SysUiState sysUiState, DumpManager dumpManager, - AutoHideController autoHideController) { + AutoHideController autoHideController, + LightBarController lightBarController) { // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; mOverviewProxyService = overviewProxyService; - mNavigationBarA11yHelper = navigationBarA11yHelper; + mNavBarHelper = navBarHelper; mNavigationModeController = navigationModeController; mSysUiState = sysUiState; dumpManager.registerDumpable(this); mAutoHideController = autoHideController; + mLightBarController = lightBarController; + mLightBarTransitionsController = createLightBarTransitionsController(); + } + + // Separated into a method to keep setDependencies() clean/readable. + private LightBarTransitionsController createLightBarTransitionsController() { + return new LightBarTransitionsController(mContext, + new LightBarTransitionsController.DarkIntensityApplier() { + @Override + public void applyDarkIntensity(float darkIntensity) { + mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity); + } + + @Override + public int getTintAnimationDuration() { + return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; + } + }, mCommandQueue) { + @Override + public boolean supportsIconTintForNavMode(int navigationMode) { + // Always tint taskbar nav buttons (region sampling handles gesture bar separately). + return true; + } + }; } public void init(int displayId) { + if (mInitialized) { + return; + } mDisplayId = displayId; mCommandQueue.addCallback(this); mOverviewProxyService.addCallback(this); mEdgeBackGestureHandler.onNavigationModeChanged( mNavigationModeController.addListener(this)); - mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.init(mContext); mEdgeBackGestureHandler.onNavBarAttached(); // Initialize component callback Display display = mDisplayManager.getDisplay(displayId); @@ -154,23 +201,32 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, // Set initial state for any listeners updateSysuiFlags(); mAutoHideController.setNavigationBar(mAutoHideUiElement); + mLightBarController.setNavigationBar(mLightBarTransitionsController); + mInitialized = true; } public void destroy() { + if (!mInitialized) { + return; + } mCommandQueue.removeCallback(this); mOverviewProxyService.removeCallback(this); mNavigationModeController.removeListener(this); - mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.destroy(); mEdgeBackGestureHandler.onNavBarDetached(); if (mWindowContext != null) { mWindowContext.unregisterComponentCallbacks(this); mWindowContext = null; } mAutoHideController.setNavigationBar(null); + mLightBarTransitionsController.destroy(mContext); + mLightBarController.setNavigationBar(null); + mInitialized = false; } private void updateSysuiFlags() { - int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + int a11yFlags = mNavBarHelper.getA11yButtonState(); boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; @@ -194,6 +250,18 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, .commitUpdate(mDisplayId); } + private void updateAssistantAvailability(boolean assistantAvailable) { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable); + } catch (RemoteException e) { + Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e); + } + } + @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { @@ -233,6 +301,10 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities, String packageName) { mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior); + if (mLightBarController != null && displayId == mDisplayId) { + mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/, + BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme); + } if (mBehavior != behavior) { mBehavior = behavior; updateSysuiFlags(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 98b914672112..fce0c0c95e57 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -17,8 +17,10 @@ package com.android.systemui.qs import android.content.Intent +import android.os.Handler import android.os.UserManager import android.provider.Settings +import android.provider.Settings.Global.USER_SWITCHER_ENABLED import android.view.View import android.widget.Toast import androidx.annotation.VisibleForTesting @@ -35,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.phone.SettingsButton import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -42,6 +45,7 @@ import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener import com.android.systemui.tuner.TunerService import com.android.systemui.util.ViewController +import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import javax.inject.Named @@ -55,6 +59,7 @@ class FooterActionsController @Inject constructor( private val qsPanelController: QSPanelController, private val activityStarter: ActivityStarter, private val userManager: UserManager, + private val userTracker: UserTracker, private val userInfoController: UserInfoController, private val multiUserSwitchController: MultiUserSwitchController, private val deviceProvisionedController: DeviceProvisionedController, @@ -64,7 +69,9 @@ class FooterActionsController @Inject constructor( private val globalActionsDialog: GlobalActionsDialogLite, private val uiEventLogger: UiEventLogger, @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean, - private val buttonsVisibleState: ExpansionState + private val buttonsVisibleState: ExpansionState, + private val globalSetting: GlobalSettings, + private val handler: Handler ) : ViewController<FooterActionsView>(view) { enum class ExpansionState { COLLAPSED, EXPANDED } @@ -83,6 +90,16 @@ class FooterActionsController @Inject constructor( mView.onUserInfoChanged(picture, isGuestUser) } + private val multiUserSetting = + object : SettingObserver( + globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) { + override fun handleValueChanged(value: Int, observedChange: Boolean) { + if (observedChange) { + updateView() + } + } + } + private val onClickListener = View.OnClickListener { v -> // Don't do anything until views are unhidden. Don't do anything if the tap looks // suspicious. @@ -182,6 +199,7 @@ class FooterActionsController @Inject constructor( return } this.listening = listening + multiUserSetting.isListening = listening if (this.listening) { userInfoController.addCallback(onUserInfoChangedListener) updateView() @@ -215,4 +233,4 @@ class FooterActionsController @Inject constructor( } private fun isTunerEnabled() = tunerService.isTunerEnabled -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt index f6c89a9c66a2..dd4dc87d8a9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt @@ -16,18 +16,22 @@ package com.android.systemui.qs +import android.os.Handler import android.os.UserManager import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.FooterActionsController.ExpansionState import com.android.systemui.qs.dagger.QSFlagsModule +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.tuner.TunerService +import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import javax.inject.Named @@ -35,6 +39,7 @@ class FooterActionsControllerBuilder @Inject constructor( private val qsPanelController: QSPanelController, private val activityStarter: ActivityStarter, private val userManager: UserManager, + private val userTracker: UserTracker, private val userInfoController: UserInfoController, private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory, private val deviceProvisionedController: DeviceProvisionedController, @@ -43,7 +48,9 @@ class FooterActionsControllerBuilder @Inject constructor( private val tunerService: TunerService, private val globalActionsDialog: GlobalActionsDialogLite, private val uiEventLogger: UiEventLogger, - @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean + @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean, + private val globalSettings: GlobalSettings, + @Main private val handler: Handler ) { private lateinit var view: FooterActionsView private lateinit var buttonsVisibleState: ExpansionState @@ -60,8 +67,9 @@ class FooterActionsControllerBuilder @Inject constructor( fun build(): FooterActionsController { return FooterActionsController(view, qsPanelController, activityStarter, userManager, - userInfoController, multiUserSwitchControllerFactory.create(view), + userTracker, userInfoController, multiUserSwitchControllerFactory.create(view), deviceProvisionedController, falsingManager, metricsLogger, tunerService, - globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState) + globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState, + globalSettings, handler) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 71eb4a2e6cbb..d69deefc3477 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -103,6 +104,8 @@ public class QSPanel extends LinearLayout implements Tunable { protected LinearLayout mHorizontalContentContainer; protected QSTileLayout mTileLayout; + private float mSquishinessFraction = 1f; + private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>(); public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -179,10 +182,26 @@ public class QSPanel extends LinearLayout implements Tunable { if (mTileLayout == null) { mTileLayout = (QSTileLayout) LayoutInflater.from(mContext) .inflate(R.layout.qs_paged_tile_layout, this, false); + mTileLayout.setSquishinessFraction(mSquishinessFraction); } return mTileLayout; } + public void setSquishinessFraction(float squishinessFraction) { + if (Float.compare(squishinessFraction, mSquishinessFraction) == 0) { + return; + } + mSquishinessFraction = squishinessFraction; + if (mTileLayout == null) { + return; + } + mTileLayout.setSquishinessFraction(squishinessFraction); + if (getMeasuredWidth() == 0) { + return; + } + updateViewPositions(); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mTileLayout instanceof PagedTileLayout) { @@ -228,6 +247,39 @@ public class QSPanel extends LinearLayout implements Tunable { setMeasuredDimension(getMeasuredWidth(), height); } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + mChildrenLayoutTop.put(child, child.getTop()); + } + updateViewPositions(); + } + + private void updateViewPositions() { + if (!(mTileLayout instanceof TileLayout)) { + return; + } + TileLayout layout = (TileLayout) mTileLayout; + + // Adjust view positions based on tile squishing + int tileHeightOffset = layout.getTilesHeight() - layout.getHeight(); + + boolean move = false; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (move) { + int top = mChildrenLayoutTop.get(child); + child.setLeftTopRightBottom(child.getLeft(), top + tileHeightOffset, + child.getRight(), top + tileHeightOffset + child.getHeight()); + } + if (child == mTileLayout) { + move = true; + } + } + } + protected String getDumpableTag() { return TAG; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index f7d1b1e2f5eb..eddc206db231 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -147,6 +147,10 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr return mMediaHost; } + public void setSquishinessFraction(float squishinessFraction) { + mView.setSquishinessFraction(squishinessFraction); + } + @Override protected void onViewAttached() { mQsTileRevealController = createTileRevealController(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt index c1c146d40e38..c680cb5d18a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt @@ -1,14 +1,10 @@ package com.android.systemui.qs -import android.view.ViewGroup -import com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER import com.android.systemui.qs.dagger.QSScope import javax.inject.Inject -import javax.inject.Named @QSScope class QSSquishinessController @Inject constructor( - @Named(QQS_FOOTER) private val qqsFooterActionsView: FooterActionsView, private val qsAnimator: QSAnimator, private val qsPanelController: QSPanelController, private val quickQSPanelController: QuickQSPanelController @@ -33,23 +29,7 @@ class QSSquishinessController @Inject constructor( * Change the height of all tiles and repositions their siblings. */ private fun updateSquishiness() { - (qsPanelController.tileLayout as QSPanel.QSTileLayout).setSquishinessFraction(squishiness) - val tileLayout = quickQSPanelController.tileLayout as TileLayout - tileLayout.setSquishinessFraction(squishiness) - - // Calculate how much we should move the footer - val tileHeightOffset = tileLayout.height - tileLayout.tilesHeight - val footerTopMargin = (qqsFooterActionsView.layoutParams as ViewGroup.MarginLayoutParams) - .topMargin - val nextTop = tileLayout.bottom - tileHeightOffset + footerTopMargin - val amountMoved = nextTop - qqsFooterActionsView.top - - // Move the footer and other siblings (MediaPlayer) - (qqsFooterActionsView.parent as ViewGroup?)?.let { parent -> - val index = parent.indexOfChild(qqsFooterActionsView) - for (i in index until parent.childCount) { - parent.getChildAt(i).top += amountMoved - } - } + qsPanelController.setSquishinessFraction(squishiness) + quickQSPanelController.setSquishinessFraction(squishiness) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 4bdeb567074e..09fad30f02ff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -247,7 +247,10 @@ open class QSTileViewImpl @JvmOverloads constructor( } else { measuredHeight } - bottom = top + (actualHeight * squishinessFraction).toInt() + // Limit how much we affect the height, so we don't have rounding artifacts when the tile + // is too short. + val constrainedSquishiness = 0.1f + squishinessFraction * 0.9f + bottom = top + (actualHeight * constrainedSquishiness).toInt() scrollY = (actualHeight - height) / 2 } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 99cb700a324d..18b401f043d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.Intent; @@ -41,7 +42,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; -import android.view.WindowManager; import android.widget.Switch; import android.widget.Toast; @@ -53,6 +53,7 @@ import com.android.settingslib.notification.EnableZenModeDialog; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SysUIToast; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -84,6 +85,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final DndDetailAdapter mDetailAdapter; private final SharedPreferences mSharedPreferences; private final SettingObserver mSettingZenDuration; + private final DialogLaunchAnimator mDialogLaunchAnimator; private boolean mListening; private boolean mShowingDetail; @@ -100,7 +102,8 @@ public class DndTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, ZenModeController zenModeController, @Main SharedPreferences sharedPreferences, - SecureSettings secureSettings + SecureSettings secureSettings, + DialogLaunchAnimator dialogLaunchAnimator ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -108,6 +111,7 @@ public class DndTile extends QSTileImpl<BooleanState> { mSharedPreferences = sharedPreferences; mDetailAdapter = new DndDetailAdapter(); mController.observe(getLifecycle(), mZenCallback); + mDialogLaunchAnimator = dialogLaunchAnimator; mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler, Settings.Secure.ZEN_DURATION, getHost().getUserId()) { @Override @@ -117,8 +121,6 @@ public class DndTile extends QSTileImpl<BooleanState> { }; } - - public static void setVisible(Context context, boolean visible) { Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible); } @@ -187,14 +189,17 @@ public class DndTile extends QSTileImpl<BooleanState> { switch (zenDuration) { case Settings.Secure.ZEN_DURATION_PROMPT: mUiHandler.post(() -> { - Dialog mDialog = new EnableZenModeDialog(mContext).createDialog(); - mDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(mDialog, true); - SystemUIDialog.registerDismissListener(mDialog); - SystemUIDialog.setWindowOnTop(mDialog); - mUiHandler.post(() -> mDialog.show()); - mHost.collapsePanels(); + Dialog dialog = makeZenModeDialog(); + if (view != null) { + final Dialog hostDialog = + mDialogLaunchAnimator.showFromView(dialog, view, false); + setDialogListeners(dialog, hostDialog); + } else { + // If we are not launching with animator, register default + // dismiss listener + SystemUIDialog.registerDismissListener(dialog); + dialog.show(); + } }); break; case Settings.Secure.ZEN_DURATION_FOREVER: @@ -209,6 +214,20 @@ public class DndTile extends QSTileImpl<BooleanState> { } } + private Dialog makeZenModeDialog() { + AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog) + .createDialog(); + SystemUIDialog.applyFlags(dialog); + SystemUIDialog.setShowForAllUsers(dialog, true); + return dialog; + } + + private void setDialogListeners(Dialog zenModeDialog, Dialog hostDialog) { + // Zen mode dialog is never hidden. + SystemUIDialog.registerDismissListener(zenModeDialog, hostDialog::dismiss); + zenModeDialog.setOnCancelListener(dialog -> hostDialog.cancel()); + } + @Override protected void handleSecondaryClick(@Nullable View view) { if (mController.isVolumeRestricted()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 77b9cc14fa6d..883552a1f7c3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -56,6 +56,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.Utils; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; @@ -120,6 +121,7 @@ public class InternetDialog extends SystemUIDialog implements private TextView mMobileTitleText; private TextView mMobileSummaryText; private Switch mMobileDataToggle; + private View mMobileToggleDivider; private Switch mWiFiToggle; private FrameLayout mDoneLayout; private Drawable mBackgroundOn; @@ -207,6 +209,7 @@ public class InternetDialog extends SystemUIDialog implements mSignalIcon = mDialogView.requireViewById(R.id.signal_icon); mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title); mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary); + mMobileToggleDivider = mDialogView.requireViewById(R.id.mobile_toggle_divider); mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle); mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle); mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on); @@ -378,6 +381,14 @@ public class InternetDialog extends SystemUIDialog implements mMobileNetworkLayout.setBackground( isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff); + TypedArray array = mContext.obtainStyledAttributes( + R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background}); + int dividerColor = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorSecondary); + mMobileToggleDivider.setBackgroundColor(isCarrierNetworkConnected + ? array.getColor(0, dividerColor) : dividerColor); + array.recycle(); + mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index fa874b19c2cc..3ed7e84af020 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -986,6 +986,18 @@ public class OverviewProxyService extends CurrentUserTracker implements } } + public void onNavButtonsDarkIntensityChanged(float darkIntensity) { + try { + if (mOverviewProxy != null) { + mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity); + } else { + Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity"); + } + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e); + } + } + private void updateEnabledState() { final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId(); mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 75b3592d4ac6..3cecbb71407a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -533,9 +533,13 @@ public class CommandQueue extends IStatusBar.Stub implements * @param animate {@code true} to show animations. */ public void recomputeDisableFlags(int displayId, boolean animate) { - int disabled1 = getDisabled1(displayId); - int disabled2 = getDisabled2(displayId); - disable(displayId, disabled1, disabled2, animate); + // This must update holding the lock otherwise it can clobber the disabled flags set on the + // binder thread from the disable() call + synchronized (mLock) { + int disabled1 = getDisabled1(displayId); + int disabled2 = getDisabled2(displayId); + disable(displayId, disabled1, disabled2, animate); + } } private void setDisabled(int displayId, int disabled1, int disabled2) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index e78b4f43f00a..0389a7b01c72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -791,6 +791,7 @@ public class NotificationEntryManager implements * these don't exist, although there are a couple exceptions. */ public Iterable<NotificationEntry> getPendingNotificationsIterator() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mPendingNotifications.values(); } @@ -803,6 +804,7 @@ public class NotificationEntryManager implements * @return a {@link NotificationEntry} if it has been prepared, else null */ public NotificationEntry getActiveNotificationUnfiltered(String key) { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mActiveNotifications.get(key); } @@ -811,6 +813,7 @@ public class NotificationEntryManager implements * notification doesn't exist. */ public NotificationEntry getPendingOrActiveNotif(String key) { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); NotificationEntry entry = mPendingNotifications.get(key); if (entry != null) { return entry; @@ -945,6 +948,7 @@ public class NotificationEntryManager implements * @return A read-only list of the currently active notifications */ public List<NotificationEntry> getVisibleNotifications() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mReadOnlyNotifications; } @@ -954,17 +958,20 @@ public class NotificationEntryManager implements */ @Override public Collection<NotificationEntry> getAllNotifs() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mReadOnlyAllNotifications; } @Nullable @Override public NotificationEntry getEntry(String key) { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return getPendingOrActiveNotif(key); } /** @return A count of the active notifications */ public int getActiveNotificationsCount() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mReadOnlyNotifications.size(); } @@ -972,6 +979,7 @@ public class NotificationEntryManager implements * @return {@code true} if there is at least one notification that should be visible right now */ public boolean hasActiveNotifications() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mReadOnlyNotifications.size() != 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinator.java index 992d898531b8..bd011c3847ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinator.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import androidx.annotation.NonNull; import com.android.systemui.communal.CommunalStateController; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -26,6 +27,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import java.util.concurrent.Executor; + import javax.inject.Inject; /** @@ -34,14 +37,17 @@ import javax.inject.Inject; */ @CoordinatorScope public class CommunalCoordinator implements Coordinator { + final Executor mExecutor; final CommunalStateController mCommunalStateController; final NotificationEntryManager mNotificationEntryManager; final NotificationLockscreenUserManager mNotificationLockscreenUserManager; @Inject - public CommunalCoordinator(NotificationEntryManager notificationEntryManager, + public CommunalCoordinator(@Main Executor executor, + NotificationEntryManager notificationEntryManager, NotificationLockscreenUserManager notificationLockscreenUserManager, CommunalStateController communalStateController) { + mExecutor = executor; mNotificationEntryManager = notificationEntryManager; mNotificationLockscreenUserManager = notificationLockscreenUserManager; mCommunalStateController = communalStateController; @@ -57,8 +63,10 @@ public class CommunalCoordinator implements Coordinator { final CommunalStateController.Callback mStateCallback = new CommunalStateController.Callback() { @Override public void onCommunalViewShowingChanged() { - mFilter.invalidateList(); - mNotificationEntryManager.updateNotifications("Communal mode state changed"); + mExecutor.execute(() -> { + mFilter.invalidateList(); + mNotificationEntryManager.updateNotifications("Communal mode state changed"); + }); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 9411de75ba7f..999ef9c99dd2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -47,7 +47,7 @@ import com.android.wm.shell.bubbles.Bubbles import dagger.Lazy import java.io.FileDescriptor import java.io.PrintWriter -import java.util.* +import java.util.Optional import javax.inject.Inject /** @@ -152,8 +152,14 @@ class NotificationsControllerImpl @Inject constructor( } override fun resetUserExpandedStates() { - for (entry in entryManager.visibleNotifications) { - entry.resetUserExpansion() + if (notifPipelineFlags.isNewPipelineEnabled()) { + for (entry in notifPipeline.get().allNotifs) { + entry.resetUserExpansion() + } + } else { + for (entry in entryManager.visibleNotifications) { + entry.resetUserExpansion() + } } } @@ -167,9 +173,12 @@ class NotificationsControllerImpl @Inject constructor( } } - override fun getActiveNotificationsCount(): Int { - return entryManager.activeNotificationsCount - } + override fun getActiveNotificationsCount(): Int = + if (notifPipelineFlags.isNewPipelineEnabled()) { + notifPipeline.get().getShadeListCount() + } else { + entryManager.activeNotificationsCount + } companion object { // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index eb7410c08733..1038e76234ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -468,8 +468,8 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc final int height = (view instanceof ExpandableView) ? ((ExpandableView) view).getActualHeight() : view.getHeight(); - final int rx = (int) ev.getX(); - final int ry = (int) ev.getY(); + final int rx = (int) ev.getRawX(); + final int ry = (int) ev.getRawY(); int[] temp = new int[2]; view.getLocationOnScreen(temp); final int x = temp[0]; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 67f51cb9ab43..aa3b3e12b8f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; @@ -46,9 +47,11 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.FileDescriptor; @@ -71,6 +74,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); + private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3; @IntDef(prefix = { "MODE_" }, value = { MODE_NONE, @@ -167,6 +171,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; + private final StatusBarStateController mStatusBarStateController; + + private long mLastFpFailureUptimeMillis; + private int mNumConsecutiveFpFailures; private static final class PendingAuthenticated { public final int userId; @@ -209,7 +217,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp BIOMETRIC_IRIS_FAILURE(403), @UiEvent(doc = "A biometric event of type iris errored.") - BIOMETRIC_IRIS_ERROR(404); + BIOMETRIC_IRIS_ERROR(404), + + @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.") + BIOMETRIC_BOUNCER_SHOWN(916); private final int mId; @@ -257,7 +268,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp NotificationMediaManager notificationMediaManager, WakefulnessLifecycle wakefulnessLifecycle, ScreenLifecycle screenLifecycle, - AuthController authController) { + AuthController authController, + StatusBarStateController statusBarStateController) { mContext = context; mPowerManager = powerManager; mShadeController = shadeController; @@ -279,6 +291,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardBypassController.setUnlockController(this); mMetricsLogger = metricsLogger; mAuthController = authController; + mStatusBarStateController = statusBarStateController; dumpManager.registerDumpable(getClass().getName(), this); } @@ -620,6 +633,22 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp .setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType))); Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) .ifPresent(UI_EVENT_LOGGER::log); + + long currUptimeMillis = SystemClock.uptimeMillis(); + if (currUptimeMillis - mLastFpFailureUptimeMillis < 2000) { // attempt within 2 seconds + mNumConsecutiveFpFailures += 1; + } else { + mNumConsecutiveFpFailures = 1; + } + mLastFpFailureUptimeMillis = currUptimeMillis; + + if (biometricSourceType.equals(BiometricSourceType.FINGERPRINT) + && mUpdateMonitor.isUdfpsSupported() + && mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) { + mKeyguardViewController.showBouncer(true); + UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN); + mNumConsecutiveFpFailures = 0; + } cleanup(); } @@ -631,6 +660,16 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp .addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId)); Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) .ifPresent(UI_EVENT_LOGGER::log); + + // if we're on the shade and we're locked out, immediately show the bouncer + if (biometricSourceType == BiometricSourceType.FINGERPRINT + && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT + || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) + && mUpdateMonitor.isUdfpsSupported() + && (mStatusBarStateController.getState() == StatusBarState.SHADE + || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) { + mKeyguardViewController.showBouncer(true); + } cleanup(); } @@ -664,6 +703,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mBiometricModeListener.onResetMode(); mBiometricModeListener.notifyBiometricAuthModeChanged(); } + mNumConsecutiveFpFailures = 0; + mLastFpFailureUptimeMillis = 0; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 353868ba969f..9647486be992 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -21,6 +21,7 @@ import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import android.content.Context; import android.content.res.ColorStateList; +import android.hardware.biometrics.BiometricSourceType; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; @@ -81,6 +82,13 @@ public class KeyguardBouncer { public void onStrongAuthStateChanged(int userId) { mBouncerPromptReason = mCallback.getBouncerPromptReason(); } + + @Override + public void onLockedOutStateChanged(BiometricSourceType type) { + if (type == BiometricSourceType.FINGERPRINT) { + mBouncerPromptReason = mCallback.getBouncerPromptReason(); + } + } }; private final Runnable mRemoveViewRunnable = this::removeView; private final KeyguardBypassController mKeyguardBypassController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 570b0ca3564c..88ae0db5bad0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -37,7 +37,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.policy.BatteryController; import java.io.FileDescriptor; @@ -251,7 +250,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private void updateNavigation() { if (mNavigationBarController != null - && !QuickStepContract.isGesturalMode(mNavigationMode)) { + && mNavigationBarController.supportsIconTintForNavMode(mNavigationMode)) { mNavigationBarController.setIconsDark(mNavigationLight, animateChange()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index 9021b74d1518..415fb92e37ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -28,6 +28,7 @@ import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.animation.Interpolators; 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.CommandQueue.Callbacks; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -230,6 +231,14 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, } /** + * Return whether to use the tint calculated in this class for nav icons. + */ + public boolean supportsIconTintForNavMode(int navigationMode) { + // In gesture mode, we already do region sampling to update tint based on content beneath. + return !QuickStepContract.isGesturalMode(navigationMode); + } + + /** * Interface to apply a specific dark intensity. */ public interface DarkIntensityApplier { 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 a9753acc2de0..16aac4d765a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -506,23 +506,11 @@ public class NotificationPanelViewController extends PanelViewController { mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN); private final NotificationEntryManager mEntryManager; - private final CommunalSourceMonitor.Callback mCommunalSourceMonitorCallback = - new CommunalSourceMonitor.Callback() { - @Override - public void onSourceAvailable(WeakReference<CommunalSource> source) { - setCommunalSource(source); - } - }; + private final CommunalSourceMonitor.Callback mCommunalSourceMonitorCallback; private WeakReference<CommunalSource> mCommunalSource; - private final CommunalSource.Callback mCommunalSourceCallback = - new CommunalSource.Callback() { - @Override - public void onDisconnected() { - setCommunalSource(null /*source*/); - } - }; + private final CommunalSource.Callback mCommunalSourceCallback; private final CommandQueue mCommandQueue; private final NotificationLockscreenUserManager mLockscreenUserManager; @@ -904,6 +892,15 @@ public class NotificationPanelViewController extends PanelViewController { mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count); mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition()); + + mCommunalSourceCallback = () -> { + mUiExecutor.execute(() -> setCommunalSource(null /*source*/)); + }; + + mCommunalSourceMonitorCallback = (source) -> { + mUiExecutor.execute(() -> setCommunalSource(source)); + }; + updateUserSwitcherFlags(); onFinishInflate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java index a77a097f0453..ae3b7ee1c809 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -30,11 +30,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.media.AudioAttributes; import android.os.Bundle; import android.os.PowerManager; import android.os.SystemClock; import android.os.UserHandle; +import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; @@ -106,10 +106,8 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { private final VibrationEffect mCameraLaunchGestureVibrationEffect; - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); @Inject StatusBarCommandQueueCallbacks( @@ -611,9 +609,9 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { } private void vibrateForCameraGesture() { - // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. mVibratorOptional.ifPresent( - v -> v.vibrate(mCameraLaunchGestureVibrationEffect, VIBRATION_ATTRIBUTES)); + v -> v.vibrate(mCameraLaunchGestureVibrationEffect, + HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES)); } private static VibrationEffect getCameraGestureVibrationEffect( @@ -627,6 +625,8 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { .compose(); } if (vibratorOptional.isPresent() && vibratorOptional.get().hasAmplitudeControl()) { + // Make sure to pass -1 for repeat so VibratorManagerService doesn't stop us when going + // to sleep. return VibrationEffect.createWaveform( StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS, StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 7ab4a1ec237e..0d23d663c51c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -441,6 +441,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)} */ public void showBouncer(boolean scrimmed) { + resetAlternateAuth(false); + if (mShowing && !mBouncer.isShowing()) { mBouncer.show(false /* resetSecuritySelection */, scrimmed); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 1130ec24108a..ed52a81751dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -36,6 +36,8 @@ import android.view.WindowInsets.Type; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import androidx.annotation.Nullable; + import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.DialogListener; @@ -303,13 +305,32 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog, * the screen off / close system dialogs broadcast. * <p> * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after - * calling this because it causes a leak of BroadcastReceiver. + * calling this because it causes a leak of BroadcastReceiver. Instead, call the version that + * takes an extra Runnable as a parameter. * * @param dialog The dialog to be associated with the listener. */ public static void registerDismissListener(Dialog dialog) { + registerDismissListener(dialog, null); + } + + + /** + * Registers a listener that dismisses the given dialog when it receives + * the screen off / close system dialogs broadcast. + * <p> + * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after + * calling this because it causes a leak of BroadcastReceiver. + * + * @param dialog The dialog to be associated with the listener. + * @param dismissAction An action to run when the dialog is dismissed. + */ + public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) { DismissReceiver dismissReceiver = new DismissReceiver(dialog); - dialog.setOnDismissListener(d -> dismissReceiver.unregister()); + dialog.setOnDismissListener(d -> { + dismissReceiver.unregister(); + if (dismissAction != null) dismissAction.run(); + }); dismissReceiver.register(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt index 58e0cb259bb2..3696ec540baf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt @@ -16,22 +16,53 @@ package com.android.systemui.animation +import android.graphics.drawable.Drawable import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import android.widget.LinearLayout +import android.view.View +import android.view.ViewGroup +import android.view.ViewParent import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() { + @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock lateinit var view: View + @Mock lateinit var rootView: ViewGroup + @Mock lateinit var viewParent: ViewParent + @Mock lateinit var drawable: Drawable + lateinit var controller: GhostedViewLaunchAnimatorController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(view.rootView).thenReturn(rootView) + whenever(view.background).thenReturn(drawable) + whenever(view.height).thenReturn(0) + whenever(view.width).thenReturn(0) + whenever(view.parent).thenReturn(viewParent) + whenever(view.visibility).thenReturn(View.VISIBLE) + whenever(view.invalidate()).then { /* NO-OP */ } + whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ } + whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true) + whenever(interactionJankMonitor.end(anyInt())).thenReturn(true) + controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor) + } + @Test fun animatingOrphanViewDoesNotCrash() { - val ghostedView = LinearLayout(mContext) - val controller = GhostedViewLaunchAnimatorController(ghostedView) val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0) controller.onIntentStarted(willAnimate = true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index 0e86964147d7..1cf21ac40e31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -351,9 +351,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mSystemClock.advanceTime(205); mController.onTouchOutsideView(); - // THEN show the bouncer and reset alt auth + // THEN show the bouncer verify(mStatusBarKeyguardViewManager).showBouncer(eq(true)); - verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java new file mode 100644 index 000000000000..659b1a31a7a7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java @@ -0,0 +1,169 @@ +/* + * 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 com.android.systemui.communal; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.testing.AndroidTestingRunner; + +import androidx.concurrent.futures.CallbackToFutureAdapter; +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class CommunalSourcePrimerTest extends SysuiTestCase { + private static final String TEST_COMPONENT_NAME = "com.google.tests/.CommunalService"; + private static final int MAX_RETRIES = 5; + private static final int RETRY_DELAY_MS = 1000; + + @Mock + private Context mContext; + + @Mock + private Resources mResources; + + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + + @Mock + private CommunalSource mSource; + + @Mock + private CommunalSourceMonitor mCommunalSourceMonitor; + + @Mock + private CommunalSource.Connector mConnector; + + @Mock + private CommunalSource.Observer mObserver; + + private CommunalSourcePrimer mPrimer; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mResources.getInteger(R.integer.config_communalSourceMaxReconnectAttempts)) + .thenReturn(MAX_RETRIES); + when(mResources.getInteger(R.integer.config_communalSourceReconnectBaseDelay)) + .thenReturn(RETRY_DELAY_MS); + when(mResources.getString(R.string.config_communalSourceComponent)) + .thenReturn(TEST_COMPONENT_NAME); + + mPrimer = new CommunalSourcePrimer(mContext, mResources, mFakeExecutor, + mCommunalSourceMonitor, Optional.of(mConnector), Optional.of(mObserver)); + } + + @Test + public void testConnect() { + when(mConnector.connect()).thenReturn( + CallbackToFutureAdapter.getFuture(completer -> { + completer.set(Optional.of(mSource)); + return "test"; + })); + + mPrimer.onBootCompleted(); + mFakeExecutor.runAllReady(); + verify(mCommunalSourceMonitor).setSource(mSource); + } + + @Test + public void testRetryOnBindFailure() throws Exception { + when(mConnector.connect()).thenReturn( + CallbackToFutureAdapter.getFuture(completer -> { + completer.set(Optional.empty()); + return "test"; + })); + + mPrimer.onBootCompleted(); + mFakeExecutor.runAllReady(); + + // Verify attempts happen. Note that we account for the retries plus initial attempt, which + // is not scheduled. + for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) { + verify(mConnector, times(1)).connect(); + clearInvocations(mConnector); + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runAllReady(); + } + + verify(mCommunalSourceMonitor, never()).setSource(Mockito.notNull()); + } + + @Test + public void testAttemptOnPackageChange() { + when(mConnector.connect()).thenReturn( + CallbackToFutureAdapter.getFuture(completer -> { + completer.set(Optional.empty()); + return "test"; + })); + + mPrimer.onBootCompleted(); + mFakeExecutor.runAllReady(); + + final ArgumentCaptor<CommunalSource.Observer.Callback> callbackCaptor = + ArgumentCaptor.forClass(CommunalSource.Observer.Callback.class); + verify(mObserver).addCallback(callbackCaptor.capture()); + + clearInvocations(mConnector); + callbackCaptor.getValue().onSourceChanged(); + + verify(mConnector, times(1)).connect(); + } + + @Test + public void testDisconnect() { + final ArgumentCaptor<CommunalSource.Callback> callbackCaptor = + ArgumentCaptor.forClass(CommunalSource.Callback.class); + + when(mConnector.connect()).thenReturn( + CallbackToFutureAdapter.getFuture(completer -> { + completer.set(Optional.of(mSource)); + return "test"; + })); + + mPrimer.onBootCompleted(); + mFakeExecutor.runAllReady(); + verify(mCommunalSourceMonitor).setSource(mSource); + verify(mSource).addCallback(callbackCaptor.capture()); + + clearInvocations(mConnector); + callbackCaptor.getValue().onDisconnected(); + mFakeExecutor.runAllReady(); + + verify(mConnector).connect(); + } +} 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..b774daf157c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -41,6 +41,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; @@ -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,9 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; +import java.util.function.Function; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest @@ -103,6 +104,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback; + private @Mock InteractionJankMonitor mInteractionJankMonitor; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -121,6 +123,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { .thenReturn(mUnfoldAnimationOptional); when(mUnfoldAnimationOptional.isPresent()).thenReturn(true); when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation); + when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true); + when(mInteractionJankMonitor.end(anyInt())).thenReturn(true); mViewMediator = new KeyguardViewMediator( mContext, @@ -144,7 +148,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mKeyguardStateController, () -> mKeyguardUnlockAnimationController, mUnlockedScreenOffAnimationController, - () -> mNotificationShadeDepthController); + () -> mNotificationShadeDepthController, + mInteractionJankMonitor); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt index 175ec87fd943..a6e567ea8b5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt @@ -104,37 +104,54 @@ class MediaCarouselControllerTest : SysuiTestCase() { fun testPlayerOrdering() { // Test values: key, data, last active time val playingLocal = Triple("playing local", - DATA.copy(active = true, isPlaying = true, isLocalSession = true, resumption = false), + DATA.copy(active = true, isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false), 4500L) - val playingRemote = Triple("playing remote", - DATA.copy(active = true, isPlaying = true, isLocalSession = false, resumption = false), + val playingCast = Triple("playing cast", + DATA.copy(active = true, isPlaying = true, + playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false), 5000L) val pausedLocal = Triple("paused local", - DATA.copy(active = true, isPlaying = false, isLocalSession = true, resumption = false), + DATA.copy(active = true, isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false), 1000L) - val pausedRemote = Triple("paused remote", - DATA.copy(active = true, isPlaying = false, isLocalSession = false, resumption = false), + val pausedCast = Triple("paused cast", + DATA.copy(active = true, isPlaying = false, + playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false), 2000L) + val playingRcn = Triple("playing RCN", + DATA.copy(active = true, isPlaying = true, + playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false), + 5000L) + + val pausedRcn = Triple("paused RCN", + DATA.copy(active = true, isPlaying = false, + playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false), + 5000L) + val resume1 = Triple("resume 1", - DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true), + DATA.copy(active = false, isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true), 500L) val resume2 = Triple("resume 2", - DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true), + DATA.copy(active = false, isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true), 1000L) // Expected ordering for media players: // Actively playing local sessions - // Actively playing remote sessions - // Paused sessions, by last active + // Actively playing cast sessions + // Paused local and cast sessions, by last active + // RCNs // Resume controls, by last active - val expected = listOf(playingLocal, playingRemote, pausedRemote, pausedLocal, resume2, - resume1) + val expected = listOf(playingLocal, playingCast, pausedCast, pausedLocal, playingRcn, + pausedRcn, resume2, resume1) expected.forEach { clock.setCurrentTimeMillis(it.third) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index 66b64708ad24..f870da3e68d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -74,8 +74,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.addListener(mListener); mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, - new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, true, - false, KEY, false, false, false, 0L); + new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, + MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 2b2fc513d03c..f44cc38c78c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.media +import android.app.Notification import android.app.Notification.MediaStyle import android.app.PendingIntent import android.app.smartspace.SmartspaceAction @@ -229,6 +230,30 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testOnNotificationAdded_isRcn_markedRemote() { + val bundle = Bundle().apply { + putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Remote Cast Notification") + } + val rcn = SbnBuilder().run { + setPkg("com.android.systemui") // System package + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + it.addExtras(bundle) + } + build() + } + + mediaDataManager.onNotificationAdded(KEY, rcn) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), + eq(false)) + assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo( + MediaData.PLAYBACK_CAST_REMOTE) + } + + @Test fun testOnNotificationRemoved_callsListener() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java)) @@ -306,7 +331,8 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), eq(false)) val data = mediaDataCaptor.value - val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, isLocalSession = false) + val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, + playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume) // WHEN the notification is removed diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt index 8dc9eff97ab9..421f9bee78fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt @@ -42,7 +42,8 @@ public class MediaPlayerDataTest : SysuiTestCase() { val mockito = MockitoJUnit.rule() companion object { - val LOCAL = true + val LOCAL = MediaData.PLAYBACK_LOCAL + val REMOTE = MediaData.PLAYBACK_CAST_LOCAL val RESUMPTION = true val PLAYING = true val UNDETERMINED = null @@ -58,7 +59,7 @@ public class MediaPlayerDataTest : SysuiTestCase() { val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION) val playerIsRemote = mock(MediaControlPanel::class.java) - val dataIsRemote = createMediaData("app2", PLAYING, !LOCAL, !RESUMPTION) + val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION) MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock) MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock) @@ -100,13 +101,13 @@ public class MediaPlayerDataTest : SysuiTestCase() { val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION) val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java) - val dataIsPlayingAndRemote = createMediaData("app2", PLAYING, !LOCAL, !RESUMPTION) + val dataIsPlayingAndRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION) val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java) val dataIsStoppedAndLocal = createMediaData("app3", !PLAYING, LOCAL, !RESUMPTION) val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java) - val dataIsStoppedAndRemote = createMediaData("app4", !PLAYING, !LOCAL, !RESUMPTION) + val dataIsStoppedAndRemote = createMediaData("app4", !PLAYING, REMOTE, !RESUMPTION) val playerCanResume = mock(MediaControlPanel::class.java) val dataCanResume = createMediaData("app5", !PLAYING, LOCAL, RESUMPTION) @@ -127,8 +128,8 @@ public class MediaPlayerDataTest : SysuiTestCase() { val players = MediaPlayerData.players() assertThat(players).hasSize(6) assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote, - playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerCanResume, - playerUndetermined).inOrder() + playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerUndetermined, + playerCanResume).inOrder() } @Test @@ -160,9 +161,10 @@ public class MediaPlayerDataTest : SysuiTestCase() { private fun createMediaData( app: String, isPlaying: Boolean?, - isLocalSession: Boolean, + location: Int, resumption: Boolean ) = - MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(), "", - null, null, null, true, null, isLocalSession, resumption, null, false, isPlaying) + MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(), + "package:" + app, null, null, null, true, null, location, resumption, "key:" + app, + false, isPlaying) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt index a17a03daba5b..30ee2e4d3431 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt @@ -211,10 +211,20 @@ class MediaResumeListenerTest : SysuiTestCase() { } @Test - fun testOnLoad_remotePlayback_doesNotCheck() { - // When media data is loaded that has not been checked yet, and is not local - val dataRemote = data.copy(isLocalSession = false) - resumeListener.onMediaDataLoaded(KEY, null, dataRemote) + fun testOnLoad_localCast_doesNotCheck() { + // When media data is loaded that has not been checked yet, and is a local cast + val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) + resumeListener.onMediaDataLoaded(KEY, null, dataCast) + + // Then we do not take action + verify(mediaDataManager, never()).setResumeAction(any(), any()) + } + + @Test + fun testOnload_remoteCast_doesNotCheck() { + // When media data is loaded that has not been checked yet, and is a remote cast + val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE) + resumeListener.onMediaDataLoaded(KEY, null, dataRcn) // Then we do not take action verify(mediaDataManager, never()).setResumeAction(any(), any()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java new file mode 100644 index 000000000000..734faec4ec74 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java @@ -0,0 +1,178 @@ +/* + * 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 com.android.systemui.navigationbar; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.assist.AssistManager; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import dagger.Lazy; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NavBarHelperTest extends SysuiTestCase { + + @Mock + AccessibilityManager mAccessibilityManager; + @Mock + AccessibilityManagerWrapper mAccessibilityManagerWrapper; + @Mock + AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + @Mock + OverviewProxyService mOverviewProxyService; + @Mock + Lazy<AssistManager> mAssistManagerLazy; + @Mock + AssistManager mAssistManager; + @Mock + NavigationModeController mNavigationModeController; + @Mock + UserTracker mUserTracker; + @Mock + ComponentName mAssistantComponent; + @Mock + DumpManager mDumpManager; + @Mock + NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater; + + private NavBarHelper mNavBarHelper; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mAssistManagerLazy.get()).thenReturn(mAssistManager); + when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent); + when(mUserTracker.getUserId()).thenReturn(1); + + mNavBarHelper = new NavBarHelper(mAccessibilityManager, + mAccessibilityManagerWrapper, mAccessibilityButtonModeObserver, + mOverviewProxyService, mAssistManagerLazy, mNavigationModeController, + mUserTracker, mDumpManager); + + } + + @Test + public void registerListenersInCtor() { + verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper); + verify(mNavigationModeController, times(1)).addListener(mNavBarHelper); + verify(mOverviewProxyService, times(1)).addCallback(mNavBarHelper); + } + + @Test + public void registerAssistantContentObserver() { + mNavBarHelper.init(mContext); + verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt()); + } + + @Test + public void callbacksFiredWhenRegistering() { + mNavBarHelper.init(mContext); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void assistantCallbacksFiredAfterConnecting() { + mNavBarHelper.init(mContext); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onConnectionChanged(false); + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + + mNavBarHelper.onConnectionChanged(true); + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void a11yCallbacksFiredAfterModeChange() { + mNavBarHelper.init(mContext); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onAccessibilityButtonModeChanged(0); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void assistantCallbacksFiredAfterNavModeChange() { + mNavBarHelper.init(mContext); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onNavigationModeChanged(0); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void removeListenerNoCallbacksFired() { + mNavBarHelper.init(mContext); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + // Remove listener + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + // Would have fired 2nd callback if not removed + mNavBarHelper.onAccessibilityButtonModeChanged(0); + + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 4fc329ffc7af..9d2541c0150f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -45,6 +45,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.After; @@ -82,11 +83,12 @@ public class NavigationBarControllerTest extends SysuiTestCase { mCommandQueue, Dependency.get(Dependency.MAIN_HANDLER), mock(ConfigurationController.class), - mock(NavigationBarA11yHelper.class), + mock(NavBarHelper.class), mock(TaskbarDelegate.class), mNavigationBarFactory, mock(DumpManager.class), - mock(AutoHideController.class))); + mock(AutoHideController.class), + mock(LightBarController.class))); initializeNavigationBars(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 50b717181dc2..e038b6e6dfb3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -136,7 +136,7 @@ public class NavigationBarTest extends SysuiTestCase { @Mock EdgeBackGestureHandler mEdgeBackGestureHandler; @Mock - NavigationBarA11yHelper mNavigationBarA11yHelper; + NavBarHelper mNavBarHelper; @Mock private LightBarController mLightBarController; @Mock @@ -227,6 +227,7 @@ public class NavigationBarTest extends SysuiTestCase { new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI) .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100) .build()); + when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true); mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain( @@ -330,14 +331,14 @@ public class NavigationBarTest extends SysuiTestCase { public void testA11yEventAfterDetach() { View v = mNavigationBar.createView(null); mNavigationBar.onViewAttachedToWindow(v); - verify(mNavigationBarA11yHelper).registerA11yEventListener(any( - NavigationBarA11yHelper.NavA11yEventListener.class)); + verify(mNavBarHelper).registerNavTaskStateUpdater(any( + NavBarHelper.NavbarTaskbarStateUpdater.class)); mNavigationBar.onViewDetachedFromWindow(v); - verify(mNavigationBarA11yHelper).removeA11yEventListener(any( - NavigationBarA11yHelper.NavA11yEventListener.class)); + verify(mNavBarHelper).removeNavTaskStateUpdater(any( + NavBarHelper.NavbarTaskbarStateUpdater.class)); // Should be safe even though the internal view is now null. - mNavigationBar.updateAccessibilityServicesState(); + mNavigationBar.updateAcessibilityStateFlags(); } private NavigationBar createNavBar(Context context) { @@ -367,7 +368,7 @@ public class NavigationBarTest extends SysuiTestCase { mHandler, mock(NavigationBarOverlayController.class), mUiEventLogger, - mNavigationBarA11yHelper, + mNavBarHelper, mock(UserTracker.class), mLightBarController, mLightBarcontrollerFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index d2bba361b1f8..26f04fc4e7b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -1,6 +1,8 @@ package com.android.systemui.qs +import android.os.Handler import android.os.UserManager +import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils @@ -16,10 +18,12 @@ import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.FooterActionsController.ExpansionState +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.tuner.TunerService +import com.android.systemui.util.settings.FakeSettings import com.android.systemui.utils.leaks.FakeTunerService import com.android.systemui.utils.leaks.LeakCheckedTest import com.google.common.truth.Truth.assertThat @@ -42,6 +46,8 @@ class FooterActionsControllerTest : LeakCheckedTest() { @Mock private lateinit var userManager: UserManager @Mock + private lateinit var userTracker: UserTracker + @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @@ -62,11 +68,13 @@ class FooterActionsControllerTest : LeakCheckedTest() { private lateinit var view: FooterActionsView private val falsingManager: FalsingManagerFake = FalsingManagerFake() private lateinit var testableLooper: TestableLooper + private lateinit var fakeSettings: FakeSettings @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + fakeSettings = FakeSettings() injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES) val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService @@ -74,10 +82,11 @@ class FooterActionsControllerTest : LeakCheckedTest() { .inflate(R.layout.footer_actions, null) as FooterActionsView controller = FooterActionsController(view, qsPanelController, activityStarter, - userManager, userInfoController, multiUserSwitchController, + userManager, userTracker, userInfoController, multiUserSwitchController, deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService, globalActionsDialog, uiEventLogger, showPMLiteButton = true, - buttonsVisibleState = ExpansionState.EXPANDED) + buttonsVisibleState = ExpansionState.EXPANDED, fakeSettings, + Handler(testableLooper.looper)) controller.init() ViewUtils.attachView(view) // View looper is the testable looper associated with the test @@ -122,4 +131,24 @@ class FooterActionsControllerTest : LeakCheckedTest() { assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE) } + + @Test + fun testMultiUserSwitchUpdatedWhenSettingChanged() { + // When expanded, listening is true + controller.setListening(true) + testableLooper.processAllMessages() + + val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch) + assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE) + + // The setting is only used as an indicator for whether the view should refresh. The actual + // value of the setting is ignored; isMultiUserEnabled is the source of truth + whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true) + + // Changing the value of USER_SWITCHER_ENABLED should cause the view to update + fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId) + testableLooper.processAllMessages() + + assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt index f41d7b127a9e..e2a0626d9849 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt @@ -1,7 +1,6 @@ package com.android.systemui.qs import android.testing.AndroidTestingRunner -import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Before @@ -9,7 +8,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @@ -18,13 +16,9 @@ import org.mockito.junit.MockitoJUnit @SmallTest class QSSquishinessControllerTest : SysuiTestCase() { - @Mock private lateinit var qqsFooterActionsView: FooterActionsView - @Mock private lateinit var qqsFooterActionsViewLP: ViewGroup.MarginLayoutParams @Mock private lateinit var qsAnimator: QSAnimator @Mock private lateinit var qsPanelController: QSPanelController @Mock private lateinit var quickQsPanelController: QuickQSPanelController - @Mock private lateinit var tileLayout: TileLayout - @Mock private lateinit var pagedTileLayout: PagedTileLayout @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @@ -32,11 +26,8 @@ class QSSquishinessControllerTest : SysuiTestCase() { @Before fun setup() { - qsSquishinessController = QSSquishinessController(qqsFooterActionsView, qsAnimator, + qsSquishinessController = QSSquishinessController(qsAnimator, qsPanelController, quickQsPanelController) - `when`(quickQsPanelController.tileLayout).thenReturn(tileLayout) - `when`(qsPanelController.tileLayout).thenReturn(pagedTileLayout) - `when`(qqsFooterActionsView.layoutParams).thenReturn(qqsFooterActionsViewLP) } @Test @@ -51,7 +42,7 @@ class QSSquishinessControllerTest : SysuiTestCase() { @Test fun setSquishiness_updatesTiles() { qsSquishinessController.squishiness = 0.5f - verify(tileLayout).setSquishinessFraction(0.5f) - verify(pagedTileLayout).setSquishinessFraction(0.5f) + verify(qsPanelController).setSquishinessFraction(0.5f) + verify(quickQsPanelController).setSquishinessFraction(0.5f) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index f99703e2415d..3ea2cc582ba3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -16,22 +16,28 @@ package com.android.systemui.qs.tiles +import android.app.Dialog import android.content.ContextWrapper import android.content.SharedPreferences import android.os.Handler import android.provider.Settings +import android.provider.Settings.Global.ZEN_MODE_OFF import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat @@ -40,9 +46,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.io.File +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -70,6 +79,10 @@ class DndTileTest : SysuiTestCase() { private lateinit var zenModeController: ZenModeController @Mock private lateinit var sharedPreferences: SharedPreferences + @Mock + private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Mock + private lateinit var hostDialog: Dialog private lateinit var secureSettings: SecureSettings private lateinit var testableLooper: TestableLooper @@ -81,15 +94,17 @@ class DndTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) secureSettings = FakeSettings() - Mockito.`when`(qsHost.userId).thenReturn(DEFAULT_USER) - Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) + whenever(qsHost.userId).thenReturn(DEFAULT_USER) + whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) + whenever(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean())) + .thenReturn(hostDialog) val wrappedContext = object : ContextWrapper(context) { override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { return sharedPreferences } } - Mockito.`when`(qsHost.context).thenReturn(wrappedContext) + whenever(qsHost.context).thenReturn(wrappedContext) tile = DndTile( qsHost, @@ -102,7 +117,8 @@ class DndTileTest : SysuiTestCase() { qsLogger, zenModeController, sharedPreferences, - secureSettings + secureSettings, + dialogLaunchAnimator ) } @@ -147,4 +163,32 @@ class DndTileTest : SysuiTestCase() { assertThat(tile.state.forceExpandIcon).isTrue() } + + @Test + fun testLaunchDialogFromViewWhenPrompt() { + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + + secureSettings.putIntForUser(KEY, Settings.Secure.ZEN_DURATION_PROMPT, DEFAULT_USER) + testableLooper.processAllMessages() + + val view = View(context) + tile.handleClick(view) + testableLooper.processAllMessages() + + verify(dialogLaunchAnimator).showFromView(any(), eq(view), anyBoolean()) + } + + @Test + fun testNoLaunchDialogWhenNotPrompt() { + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + + secureSettings.putIntForUser(KEY, 60, DEFAULT_USER) + testableLooper.processAllMessages() + + val view = View(context) + tile.handleClick(view) + testableLooper.processAllMessages() + + verify(dialogLaunchAnimator, never()).showFromView(any(), any(), anyBoolean()) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinatorTest.java index 01e4cce0cc30..f4452bc248b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinatorTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.test.suitebuilder.annotation.SmallTest; @@ -29,6 +30,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -39,6 +42,8 @@ import org.mockito.MockitoAnnotations; @SmallTest public class CommunalCoordinatorTest extends SysuiTestCase { + private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + @Mock CommunalStateController mCommunalStateController; @Mock @@ -57,7 +62,7 @@ public class CommunalCoordinatorTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - mCoordinator = new CommunalCoordinator(mNotificationEntryManager, + mCoordinator = new CommunalCoordinator(mExecutor, mNotificationEntryManager, mNotificationLockscreenUserManager, mCommunalStateController); } @@ -84,6 +89,12 @@ public class CommunalCoordinatorTest extends SysuiTestCase { // Verify that notifications are filtered out when communal is showing and that the filter // pipeline is notified. stateCallback.onCommunalViewShowingChanged(); + // Make sure callback depends on executor to run. + verify(mFilterListener, never()).onPluggableInvalidated(any()); + verify(mNotificationEntryManager, never()).updateNotifications(any()); + + mExecutor.runAllReady(); + verify(mFilterListener).onPluggableInvalidated(any()); verify(mNotificationEntryManager).updateNotifications(any()); assert (filter.shouldFilterOut(mNotificationEntry, 0)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 0faf5d478116..a0e91fc77148 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -438,8 +438,8 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { assertEquals("returns false when view is null", false, NotificationSwipeHelper.isTouchInView(mEvent, null)); - doReturn(5f).when(mEvent).getX(); - doReturn(10f).when(mEvent).getY(); + doReturn(5f).when(mEvent).getRawX(); + doReturn(10f).when(mEvent).getRawY(); doReturn(20).when(mView).getWidth(); doReturn(20).when(mView).getHeight(); @@ -455,7 +455,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { assertTrue("Touch is within the view", mSwipeHelper.isTouchInView(mEvent, mView)); - doReturn(50f).when(mEvent).getX(); + doReturn(50f).when(mEvent).getRawX(); assertFalse("Touch is not within the view", mSwipeHelper.isTouchInView(mEvent, mView)); @@ -466,8 +466,8 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { assertEquals("returns false when view is null", false, NotificationSwipeHelper.isTouchInView(mEvent, null)); - doReturn(5f).when(mEvent).getX(); - doReturn(10f).when(mEvent).getY(); + doReturn(5f).when(mEvent).getRawX(); + doReturn(10f).when(mEvent).getRawY(); doReturn(20).when(mNotificationRow).getWidth(); doReturn(20).when(mNotificationRow).getActualHeight(); @@ -483,7 +483,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { assertTrue("Touch is within the view", mSwipeHelper.isTouchInView(mEvent, mNotificationRow)); - doReturn(50f).when(mEvent).getX(); + doReturn(50f).when(mEvent).getRawX(); assertFalse("Touch is not within the view", mSwipeHelper.isTouchInView(mEvent, mNotificationRow)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 25fd80133897..07debe68e224 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -101,6 +102,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private ScreenLifecycle mScreenLifecycle; + @Mock + private StatusBarStateController mStatusBarStateController; private BiometricUnlockController mBiometricUnlockController; @Before @@ -123,7 +126,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger, mDumpManager, mPowerManager, mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, - mAuthController); + mAuthController, mStatusBarStateController); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener); } @@ -378,6 +381,23 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test + public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() { + // GIVEN UDFPS is supported + when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true); + + // WHEN udfps fails twice - then don't show the bouncer + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean()); + + // WHEN udfps fails the third time + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + + // THEN show the bouncer + verify(mStatusBarKeyguardViewManager).showBouncer(true); + } + + @Test public void onFinishedGoingToSleep_authenticatesWhenPending() { when(mUpdateMonitor.isGoingToSleep()).thenReturn(true); mBiometricUnlockController.onFinishedGoingToSleep(-1); 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 81ddc670745b..270c64ddfa7a 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 @@ -358,6 +358,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private NotificationsQuickSettingsContainer mNotificationContainerParent; private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; private FalsingManagerFake mFalsingManager = new FalsingManagerFake(); + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setup() { @@ -511,7 +512,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mQuickAccessWalletController, mQrCodeScannerController, mRecordingController, - new FakeExecutor(new FakeSystemClock()), + mExecutor, mSecureSettings, mSplitShadeHeaderController, mUnlockedScreenOffAnimationController, @@ -936,6 +937,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(WeakReference.class); monitorCallback.getValue().onSourceAvailable(new WeakReference<>(mCommunalSource)); + mExecutor.runAllReady(); verify(mCommunalHostViewController).show(sourceCapture.capture()); assertThat(sourceCapture.getValue().get()).isEqualTo(mCommunalSource); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 6f174cbe0021..c5bdfed6082b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -374,6 +374,21 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void testHideAltAuth_onShowBouncer() { + // GIVEN alt auth is showing + mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); + when(mBouncer.isShowing()).thenReturn(false); + when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true); + reset(mAlternateAuthInterceptor); + + // WHEN showBouncer is called + mStatusBarKeyguardViewManager.showBouncer(true); + + // THEN alt bouncer should be hidden + verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer(); + } + + @Test public void testUpdateResources_delegatesToBouncer() { mStatusBarKeyguardViewManager.updateResources(); diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 67bb7262b672..f1599e4d7ffa 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -39,6 +39,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; @@ -1101,8 +1102,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ try { MagnificationProcessor magnificationProcessor = mSystemSupport.getMagnificationProcessor(); - return magnificationProcessor - .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId); + final MagnificationConfig config = new MagnificationConfig.Builder() + .setScale(scale) + .setCenterX(centerX) + .setCenterY(centerY).build(); + return magnificationProcessor.setMagnificationConfig(displayId, config, animate, + mId); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 6473bf5ffc3e..327f08753524 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -84,6 +84,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb @GuardedBy("mLock") private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE; + @GuardedBy("mLock") + private int mLastActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; // Track the active user to reset the magnification and get the associated user settings. private @UserIdInt int mUserId = UserHandle.USER_SYSTEM; @GuardedBy("mLock") @@ -239,6 +241,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + mLastActivatedMode = mActivatedMode; } logMagnificationModeWithImeOnIfNeeded(); disableFullScreenMagnificationIfNeeded(displayId); @@ -276,6 +279,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + mLastActivatedMode = mActivatedMode; } logMagnificationModeWithImeOnIfNeeded(); } else { @@ -298,6 +302,16 @@ public class MagnificationController implements WindowMagnificationManager.Callb } /** + * Returns the last activated magnification mode. If there is no activated magnifier before, it + * returns fullscreen mode by default. + */ + public int getLastActivatedMode() { + synchronized (mLock) { + return mLastActivatedMode; + } + } + + /** * Wrapper method of logging the magnification activated mode and its duration of the usage * when the magnification is disabled. * @@ -336,6 +350,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { fullMagnificationController = mFullScreenMagnificationController; windowMagnificationManager = mWindowMagnificationMgr; + mLastActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; } mScaleProvider.onUserChanged(userId); @@ -462,7 +477,15 @@ public class MagnificationController implements WindowMagnificationManager.Callb return mTempPoint; } - private boolean isActivated(int displayId, int mode) { + /** + * Return {@code true} if the specified magnification mode on the given display is activated + * or not. + * + * @param displayId The logical displayId. + * @param mode It's either ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN or + * ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW. + */ + public boolean isActivated(int displayId, int mode) { boolean isActivated = false; if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { synchronized (mLock) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java index efc6d515fdb8..2324a5a781f4 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java @@ -16,6 +16,13 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.MagnificationConfig.DEFAULT_MODE; +import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE; +import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + +import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.graphics.Region; @@ -23,6 +30,22 @@ import android.graphics.Region; * Processor class for AccessibilityService connection to control magnification on the specified * display. This wraps the function of magnification controller. * + * <p> + * If the magnification config uses {@link DEFAULT_MODE}. This processor will control the current + * activated magnifier on the display. If there is no magnifier activated, it controls + * full-screen magnifier by default. + * </p> + * + * <p> + * If the magnification config uses {@link FULLSCREEN_MODE}. This processor will control + * full-screen magnifier on the display. + * </p> + * + * <p> + * If the magnification config uses {@link WINDOW_MODE}. This processor will control + * the activated window magnifier on the display. + * </p> + * * @see MagnificationController * @see FullScreenMagnificationController */ @@ -35,53 +58,166 @@ public class MagnificationProcessor { } /** - * {@link FullScreenMagnificationController#getScale(int)} + * Gets the magnification config of the display. + * + * @param displayId The logical display id + * @return the magnification config + */ + public @NonNull MagnificationConfig getMagnificationConfig(int displayId) { + final int mode = getControllingMode(displayId); + MagnificationConfig.Builder builder = new MagnificationConfig.Builder(); + if (mode == FULLSCREEN_MODE) { + final FullScreenMagnificationController fullScreenMagnificationController = + mController.getFullScreenMagnificationController(); + builder.setMode(mode) + .setScale(fullScreenMagnificationController.getScale(displayId)) + .setCenterX(fullScreenMagnificationController.getCenterX(displayId)) + .setCenterY(fullScreenMagnificationController.getCenterY(displayId)); + } else if (mode == WINDOW_MODE) { + final WindowMagnificationManager windowMagnificationManager = + mController.getWindowMagnificationMgr(); + builder.setMode(mode) + .setScale(windowMagnificationManager.getScale(displayId)) + .setCenterX(windowMagnificationManager.getCenterX(displayId)) + .setCenterY(windowMagnificationManager.getCenterY(displayId)); + } + return builder.build(); + } + + /** + * Sets the magnification config of the display. If animation is disabled, the transition + * is immediate. + * + * @param displayId The logical display id + * @param config The magnification config + * @param animate {@code true} to animate from the current config or + * {@code false} to set the config immediately + * @param id The ID of the service requesting the change + * @return {@code true} if the magnification spec changed, {@code false} if the spec did not + * change + */ + public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, + boolean animate, int id) { + int configMode = config.getMode(); + if (configMode == DEFAULT_MODE) { + configMode = getControllingMode(displayId); + } + if (configMode == FULLSCREEN_MODE) { + return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(), + config.getCenterX(), config.getCenterY(), + animate, id); + } else if (configMode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId, + config.getScale(), config.getCenterX(), config.getCenterY()); + } + return false; + } + + /** + * Returns the magnification scale. If an animation is in progress, + * this reflects the end state of the animation. + * + * @param displayId The logical display id. + * @return the scale */ public float getScale(int displayId) { - return mController.getFullScreenMagnificationController().getScale(displayId); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + return mController.getFullScreenMagnificationController().getScale(displayId); + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().getScale(displayId); + } + return 0; } /** - * {@link FullScreenMagnificationController#getCenterX(int)} + * Returns the magnification center in X coordinate of the controlling magnification mode. + * If the service can control magnification but fullscreen magnifier is not registered, it will + * register the magnifier for this call then unregister the magnifier finally to make the + * magnification center correct. + * + * @param displayId The logical display id + * @param canControlMagnification Whether the service can control magnification + * @return the X coordinate */ public float getCenterX(int displayId, boolean canControlMagnification) { - boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId, - canControlMagnification); - try { - return mController.getFullScreenMagnificationController().getCenterX(displayId); - } finally { - if (registeredJustForThisCall) { - unregister(displayId); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, + canControlMagnification); + try { + return mController.getFullScreenMagnificationController().getCenterX(displayId); + } finally { + if (registeredJustForThisCall) { + unregister(displayId); + } } + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().getCenterX(displayId); } + return 0; } /** - * {@link FullScreenMagnificationController#getCenterY(int)} + * Returns the magnification center in Y coordinate of the controlling magnification mode. + * If the service can control magnification but fullscreen magnifier is not registered, it will + * register the magnifier for this call then unregister the magnifier finally to make the + * magnification center correct. + * + * @param displayId The logical display id + * @param canControlMagnification Whether the service can control magnification + * @return the Y coordinate */ public float getCenterY(int displayId, boolean canControlMagnification) { - boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId, - canControlMagnification); - try { - return mController.getFullScreenMagnificationController().getCenterY(displayId); - } finally { - if (registeredJustForThisCall) { - unregister(displayId); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, + canControlMagnification); + try { + return mController.getFullScreenMagnificationController().getCenterY(displayId); + } finally { + if (registeredJustForThisCall) { + unregister(displayId); + } } + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().getCenterY(displayId); } + return 0; } /** - * {@link FullScreenMagnificationController#getMagnificationRegion(int, Region)} + * Return the magnification bounds of the current controlling magnification on the given + * display. If the magnifier is not enabled, it returns an empty region. + * If the service can control magnification but fullscreen magnifier is not registered, it will + * register the magnifier for this call then unregister the magnifier finally to make + * the magnification region correct. + * + * @param displayId The logical display id + * @param outRegion the region to populate + * @param canControlMagnification Whether the service can control magnification + * @return outRegion the magnification bounds of full-screen magnifier or the magnification + * source bounds of window magnifier */ public Region getMagnificationRegion(int displayId, @NonNull Region outRegion, boolean canControlMagnification) { - boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId, + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification); + } else if (mode == WINDOW_MODE) { + mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId, + outRegion); + } + return outRegion; + } + + private void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion, + boolean canControlMagnification) { + boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, canControlMagnification); try { mController.getFullScreenMagnificationController().getMagnificationRegion(displayId, outRegion); - return outRegion; } finally { if (registeredJustForThisCall) { unregister(displayId); @@ -89,67 +225,105 @@ public class MagnificationProcessor { } } - /** - * {@link FullScreenMagnificationController#setScaleAndCenter(int, float, float, float, boolean, - * int)} - */ - public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, + private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale, + float centerX, float centerY, boolean animate, int id) { if (!isRegistered(displayId)) { register(displayId); } - return mController.getFullScreenMagnificationController().setScaleAndCenter(displayId, + return mController.getFullScreenMagnificationController().setScaleAndCenter( + displayId, scale, centerX, centerY, animate, id); } /** - * {@link FullScreenMagnificationController#reset(int, boolean)} + * Resets the magnification on the given display. The reset mode could be full-screen or + * window if it is activated. + * + * @param displayId The logical display id. + * @param animate {@code true} to animate the transition, {@code false} + * to transition immediately + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change */ public boolean reset(int displayId, boolean animate) { - return mController.getFullScreenMagnificationController().reset(displayId, animate); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + return mController.getFullScreenMagnificationController().reset(displayId, animate); + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().reset(displayId); + } + return false; } /** * {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)} */ + // TODO: support window magnification public void resetAllIfNeeded(int connectionId) { mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId); } /** - * {@link FullScreenMagnificationController#register(int)} - */ - public void register(int displayId) { - mController.getFullScreenMagnificationController().register(displayId); - } - - /** - * {@link FullScreenMagnificationController#unregister(int)} (int)} - */ - public void unregister(int displayId) { - mController.getFullScreenMagnificationController().unregister(displayId); - } - - /** * {@link FullScreenMagnificationController#isMagnifying(int)} + * {@link WindowMagnificationManager#isWindowMagnifierEnabled(int)} */ public boolean isMagnifying(int displayId) { - return mController.getFullScreenMagnificationController().isMagnifying(displayId); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + return mController.getFullScreenMagnificationController().isMagnifying(displayId); + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId); + } + return false; } /** - * {@link FullScreenMagnificationController#isRegistered(int)} + * Returns the current controlling magnification mode on the given display. + * If there is no magnifier activated, it fallbacks to the last activated mode. + * And the last activated mode is {@link FULLSCREEN_MODE} by default. + * + * @param displayId The logical display id */ - public boolean isRegistered(int displayId) { - return mController.getFullScreenMagnificationController().isRegistered(displayId); + public int getControllingMode(int displayId) { + if (mController.isActivated(displayId, + ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) { + return WINDOW_MODE; + } else if (mController.isActivated(displayId, + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) { + return FULLSCREEN_MODE; + } else { + return (mController.getLastActivatedMode() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) + ? WINDOW_MODE + : FULLSCREEN_MODE; + } } - private boolean registerMagnificationIfNeeded(int displayId, boolean canControlMagnification) { + private boolean registerDisplayMagnificationIfNeeded(int displayId, + boolean canControlMagnification) { if (!isRegistered(displayId) && canControlMagnification) { register(displayId); return true; } return false; } + + private boolean isRegistered(int displayId) { + return mController.getFullScreenMagnificationController().isRegistered(displayId); + } + + /** + * {@link FullScreenMagnificationController#register(int)} + */ + private void register(int displayId) { + mController.getFullScreenMagnificationController().register(displayId); + } + + /** + * {@link FullScreenMagnificationController#unregister(int)} (int)} + */ + private void unregister(int displayId) { + mController.getFullScreenMagnificationController().unregister(displayId); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index e1ec273b444a..d34b4a9f3ca7 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Rect; +import android.graphics.Region; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -271,9 +272,12 @@ public class WindowMagnificationManager implements * or {@link Float#NaN} to leave unchanged. * @param centerY The screen-relative Y coordinate around which to center, * or {@link Float#NaN} to leave unchanged. + * @return {@code true} if the magnification is enabled successfully. */ - void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) { - enableWindowMagnification(displayId, scale, centerX, centerY, STUB_ANIMATION_CALLBACK); + public boolean enableWindowMagnification(int displayId, float scale, float centerX, + float centerY) { + return enableWindowMagnification(displayId, scale, centerX, centerY, + STUB_ANIMATION_CALLBACK); } /** @@ -287,25 +291,29 @@ public class WindowMagnificationManager implements * @param centerY The screen-relative Y coordinate around which to center, * or {@link Float#NaN} to leave unchanged. * @param animationCallback Called when the animation result is valid. + * @return {@code true} if the magnification is enabled successfully. */ - void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, - @Nullable MagnificationAnimationCallback animationCallback) { + public boolean enableWindowMagnification(int displayId, float scale, float centerX, + float centerY, @Nullable MagnificationAnimationCallback animationCallback) { final boolean enabled; + boolean previousEnabled; synchronized (mLock) { if (mConnectionWrapper == null) { - return; + return false; } WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { magnifier = createWindowMagnifier(displayId); } + previousEnabled = magnifier.mEnabled; enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, animationCallback); } - if (enabled) { + if (enabled && !previousEnabled) { mCallback.onWindowMagnificationActivationState(displayId, true); } + return enabled; } /** @@ -464,7 +472,7 @@ public class WindowMagnificationManager implements * @param displayId The logical display id * @return the X coordinate. {@link Float#NaN} if the window magnification is not enabled. */ - float getCenterX(int displayId) { + public float getCenterX(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -480,7 +488,7 @@ public class WindowMagnificationManager implements * @param displayId The logical display id * @return the Y coordinate. {@link Float#NaN} if the window magnification is not enabled. */ - float getCenterY(int displayId) { + public float getCenterY(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -491,6 +499,42 @@ public class WindowMagnificationManager implements } /** + * Populates magnified bounds on the screen. And the populated magnified bounds would be + * empty If window magnifier is not activated. + * + * @param displayId The logical display id. + * @param outRegion the region to populate + */ + public void getMagnificationSourceBounds(int displayId, @NonNull Region outRegion) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + outRegion.setEmpty(); + } else { + outRegion.set(magnifier.mSourceBounds); + } + } + } + + /** + * Resets the magnification scale and center. + * + * @param displayId The logical display id. + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change + */ + public boolean reset(int displayId) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return false; + } + magnifier.reset(); + return true; + } + } + + /** * Creates the windowMagnifier based on the specified display and stores it. * * @param displayId logical display id. @@ -626,8 +670,9 @@ public class WindowMagnificationManager implements @GuardedBy("mLock") boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback animationCallback) { - if (mEnabled) { - return false; + // Handle defaults. The scale may be NAN when just updating magnification center. + if (Float.isNaN(scale)) { + scale = getScale(); } final float normScale = MagnificationScaleProvider.constrainScale(scale); if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale, diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java index c9608a55170e..3e0208411c21 100644 --- a/services/core/java/com/android/server/NsdService.java +++ b/services/core/java/com/android/server/NsdService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -20,24 +20,27 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; -import android.net.NetworkStack; import android.net.Uri; import android.net.nsd.INsdManager; +import android.net.nsd.INsdManagerCallback; +import android.net.nsd.INsdServiceConnector; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.Message; -import android.os.Messenger; +import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Base64; +import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AsyncChannel; import com.android.internal.util.DumpUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -72,12 +75,11 @@ public class NsdService extends INsdManager.Stub { /** * Clients receiving asynchronous messages */ - private final HashMap<Messenger, ClientInfo> mClients = new HashMap<>(); + private final HashMap<NsdServiceConnector, ClientInfo> mClients = new HashMap<>(); /* A map from unique id to client info */ private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>(); - private final AsyncChannel mReplyChannel = new AsyncChannel(); private final long mCleanupDelayMs; private static final int INVALID_ID = 0; @@ -149,65 +151,66 @@ public class NsdService extends INsdManager.Stub { class DefaultState extends State { @Override public boolean processMessage(Message msg) { - ClientInfo cInfo = null; + final ClientInfo cInfo; + final int clientId = msg.arg2; switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - AsyncChannel c = (AsyncChannel) msg.obj; - if (DBG) Slog.d(TAG, "New client listening to asynchronous messages"); - c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED); - cInfo = new ClientInfo(c, msg.replyTo); - mClients.put(msg.replyTo, cInfo); - } else { - Slog.e(TAG, "Client connection failure, error=" + msg.arg1); + case NsdManager.REGISTER_CLIENT: + final Pair<NsdServiceConnector, INsdManagerCallback> arg = + (Pair<NsdServiceConnector, INsdManagerCallback>) msg.obj; + final INsdManagerCallback cb = arg.second; + try { + cb.asBinder().linkToDeath(arg.first, 0); + cInfo = new ClientInfo(cb); + mClients.put(arg.first, cInfo); + } catch (RemoteException e) { + Log.w(TAG, "Client " + clientId + " has already died"); } break; - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: - switch (msg.arg1) { - case AsyncChannel.STATUS_SEND_UNSUCCESSFUL: - Slog.e(TAG, "Send failed, client connection lost"); - break; - case AsyncChannel.STATUS_REMOTE_DISCONNECTION: - if (DBG) Slog.d(TAG, "Client disconnected"); - break; - default: - if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); - break; - } - - cInfo = mClients.get(msg.replyTo); + case NsdManager.UNREGISTER_CLIENT: + final NsdServiceConnector connector = (NsdServiceConnector) msg.obj; + cInfo = mClients.remove(connector); if (cInfo != null) { cInfo.expungeAllRequests(); - mClients.remove(msg.replyTo); if (cInfo.isLegacy()) { mLegacyClientCount -= 1; } } maybeScheduleStop(); break; - case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: - AsyncChannel ac = new AsyncChannel(); - ac.connect(mContext, getHandler(), msg.replyTo); - break; case NsdManager.DISCOVER_SERVICES: - replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + cInfo = getClientInfoForReply(msg); + if (cInfo != null) { + cInfo.onDiscoverServicesFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); + } break; case NsdManager.STOP_DISCOVERY: - replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + cInfo = getClientInfoForReply(msg); + if (cInfo != null) { + cInfo.onStopDiscoveryFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); + } break; case NsdManager.REGISTER_SERVICE: - replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + cInfo = getClientInfoForReply(msg); + if (cInfo != null) { + cInfo.onRegisterServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); + } break; case NsdManager.UNREGISTER_SERVICE: - replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + cInfo = getClientInfoForReply(msg); + if (cInfo != null) { + cInfo.onUnregisterServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); + } break; case NsdManager.RESOLVE_SERVICE: - replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + cInfo = getClientInfoForReply(msg); + if (cInfo != null) { + cInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); + } break; case NsdManager.DAEMON_CLEANUP: mDaemon.maybeStop(); @@ -215,7 +218,7 @@ public class NsdService extends INsdManager.Stub { // This event should be only sent by the legacy (target SDK < S) clients. // Mark the sending client as legacy. case NsdManager.DAEMON_STARTUP: - cInfo = mClients.get(msg.replyTo); + cInfo = getClientInfoForReply(msg); if (cInfo != null) { cancelStop(); cInfo.setLegacy(); @@ -230,6 +233,11 @@ public class NsdService extends INsdManager.Stub { } return HANDLED; } + + private ClientInfo getClientInfoForReply(Message msg) { + final ListenerArgs args = (ListenerArgs) msg.obj; + return mClients.get(args.connector); + } } class DisabledState extends State { @@ -289,122 +297,119 @@ public class NsdService extends INsdManager.Stub { @Override public boolean processMessage(Message msg) { - ClientInfo clientInfo; - NsdServiceInfo servInfo; - int id; + final ClientInfo clientInfo; + final int id; + final int clientId = msg.arg2; + final ListenerArgs args; switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - return NOT_HANDLED; - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: - return NOT_HANDLED; case NsdManager.DISABLE: //TODO: cleanup clients transitionTo(mDisabledState); break; case NsdManager.DISCOVER_SERVICES: if (DBG) Slog.d(TAG, "Discover services"); - servInfo = (NsdServiceInfo) msg.obj; - clientInfo = mClients.get(msg.replyTo); + args = (ListenerArgs) msg.obj; + clientInfo = mClients.get(args.connector); if (requestLimitReached(clientInfo)) { - replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, - NsdManager.FAILURE_MAX_LIMIT); + clientInfo.onDiscoverServicesFailed( + clientId, NsdManager.FAILURE_MAX_LIMIT); break; } maybeStartDaemon(); id = getUniqueId(); - if (discoverServices(id, servInfo.getServiceType())) { + if (discoverServices(id, args.serviceInfo.getServiceType())) { if (DBG) { Slog.d(TAG, "Discover " + msg.arg2 + " " + id + - servInfo.getServiceType()); + args.serviceInfo.getServiceType()); } - storeRequestMap(msg.arg2, id, clientInfo, msg.what); - replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo); + storeRequestMap(clientId, id, clientInfo, msg.what); + clientInfo.onDiscoverServicesStarted(clientId, args.serviceInfo); } else { stopServiceDiscovery(id); - replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, + clientInfo.onDiscoverServicesFailed(clientId, NsdManager.FAILURE_INTERNAL_ERROR); } break; case NsdManager.STOP_DISCOVERY: if (DBG) Slog.d(TAG, "Stop service discovery"); - clientInfo = mClients.get(msg.replyTo); + args = (ListenerArgs) msg.obj; + clientInfo = mClients.get(args.connector); try { - id = clientInfo.mClientIds.get(msg.arg2); + id = clientInfo.mClientIds.get(clientId); } catch (NullPointerException e) { - replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + clientInfo.onStopDiscoveryFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); break; } - removeRequestMap(msg.arg2, id, clientInfo); + removeRequestMap(clientId, id, clientInfo); if (stopServiceDiscovery(id)) { - replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED); + clientInfo.onStopDiscoverySucceeded(clientId); } else { - replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + clientInfo.onStopDiscoveryFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); } break; case NsdManager.REGISTER_SERVICE: if (DBG) Slog.d(TAG, "Register service"); - clientInfo = mClients.get(msg.replyTo); + args = (ListenerArgs) msg.obj; + clientInfo = mClients.get(args.connector); if (requestLimitReached(clientInfo)) { - replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, - NsdManager.FAILURE_MAX_LIMIT); + clientInfo.onRegisterServiceFailed( + clientId, NsdManager.FAILURE_MAX_LIMIT); break; } maybeStartDaemon(); id = getUniqueId(); - if (registerService(id, (NsdServiceInfo) msg.obj)) { - if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id); - storeRequestMap(msg.arg2, id, clientInfo, msg.what); + if (registerService(id, args.serviceInfo)) { + if (DBG) Slog.d(TAG, "Register " + clientId + " " + id); + storeRequestMap(clientId, id, clientInfo, msg.what); // Return success after mDns reports success } else { unregisterService(id); - replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + clientInfo.onRegisterServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); } break; case NsdManager.UNREGISTER_SERVICE: if (DBG) Slog.d(TAG, "unregister service"); - clientInfo = mClients.get(msg.replyTo); - try { - id = clientInfo.mClientIds.get(msg.arg2); - } catch (NullPointerException e) { - replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + args = (ListenerArgs) msg.obj; + clientInfo = mClients.get(args.connector); + if (clientInfo == null) { + Slog.e(TAG, "Unknown connector in unregistration"); break; } - removeRequestMap(msg.arg2, id, clientInfo); + id = clientInfo.mClientIds.get(clientId); + removeRequestMap(clientId, id, clientInfo); if (unregisterService(id)) { - replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED); + clientInfo.onUnregisterServiceSucceeded(clientId); } else { - replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + clientInfo.onUnregisterServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); } break; case NsdManager.RESOLVE_SERVICE: if (DBG) Slog.d(TAG, "Resolve service"); - servInfo = (NsdServiceInfo) msg.obj; - clientInfo = mClients.get(msg.replyTo); - + args = (ListenerArgs) msg.obj; + clientInfo = mClients.get(args.connector); if (clientInfo.mResolvedService != null) { - replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.FAILURE_ALREADY_ACTIVE); + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_ALREADY_ACTIVE); break; } maybeStartDaemon(); id = getUniqueId(); - if (resolveService(id, servInfo)) { + if (resolveService(id, args.serviceInfo)) { clientInfo.mResolvedService = new NsdServiceInfo(); - storeRequestMap(msg.arg2, id, clientInfo, msg.what); + storeRequestMap(clientId, id, clientInfo, msg.what); } else { - replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR); + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); } break; case NsdManager.NATIVE_DAEMON_EVENT: @@ -449,30 +454,27 @@ public class NsdService extends INsdManager.Stub { case NativeResponseCode.SERVICE_FOUND: /* NNN uniqueId serviceName regType domain */ servInfo = new NsdServiceInfo(cooked[2], cooked[3]); - clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0, - clientId, servInfo); + clientInfo.onServiceFound(clientId, servInfo); break; case NativeResponseCode.SERVICE_LOST: /* NNN uniqueId serviceName regType domain */ servInfo = new NsdServiceInfo(cooked[2], cooked[3]); - clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0, - clientId, servInfo); + clientInfo.onServiceLost(clientId, servInfo); break; case NativeResponseCode.SERVICE_DISCOVERY_FAILED: /* NNN uniqueId errorCode */ - clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR, clientId); + clientInfo.onDiscoverServicesFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); break; case NativeResponseCode.SERVICE_REGISTERED: /* NNN regId serviceName regType */ servInfo = new NsdServiceInfo(cooked[2], null); - clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED, - id, clientId, servInfo); + clientInfo.onRegisterServiceSucceeded(clientId, servInfo); break; case NativeResponseCode.SERVICE_REGISTRATION_FAILED: /* NNN regId errorCode */ - clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR, clientId); + clientInfo.onRegisterServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); break; case NativeResponseCode.SERVICE_UPDATED: /* NNN regId */ @@ -511,8 +513,8 @@ public class NsdService extends INsdManager.Stub { if (getAddrInfo(id2, cooked[3])) { storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE); } else { - clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR, clientId); + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); clientInfo.mResolvedService = null; } break; @@ -521,26 +523,26 @@ public class NsdService extends INsdManager.Stub { stopResolveService(id); removeRequestMap(clientId, id, clientInfo); clientInfo.mResolvedService = null; - clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR, clientId); + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); break; case NativeResponseCode.SERVICE_GET_ADDR_FAILED: /* NNN resolveId errorCode */ stopGetAddrInfo(id); removeRequestMap(clientId, id, clientInfo); clientInfo.mResolvedService = null; - clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR, clientId); + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); break; case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS: /* NNN resolveId hostname ttl addr */ try { clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4])); - clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED, - 0, clientId, clientInfo.mResolvedService); + clientInfo.onResolveServiceSucceeded( + clientId, clientInfo.mResolvedService); } catch (java.net.UnknownHostException e) { - clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, - NsdManager.FAILURE_INTERNAL_ERROR, clientId); + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); } stopGetAddrInfo(id); removeRequestMap(clientId, id, clientInfo); @@ -601,15 +603,71 @@ public class NsdService extends INsdManager.Stub { return service; } - public Messenger getMessenger() { + @Override + public INsdServiceConnector connect(INsdManagerCallback cb) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService"); - return new Messenger(mNsdStateMachine.getHandler()); + final INsdServiceConnector connector = new NsdServiceConnector(); + mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage( + NsdManager.REGISTER_CLIENT, new Pair<>(connector, cb))); + return connector; + } + + private static class ListenerArgs { + public final NsdServiceConnector connector; + public final NsdServiceInfo serviceInfo; + ListenerArgs(NsdServiceConnector connector, NsdServiceInfo serviceInfo) { + this.connector = connector; + this.serviceInfo = serviceInfo; + } } - public void setEnabled(boolean isEnabled) { - NetworkStack.checkNetworkStackPermission(mContext); - mNsdSettings.putEnabledStatus(isEnabled); - notifyEnabled(isEnabled); + private class NsdServiceConnector extends INsdServiceConnector.Stub + implements IBinder.DeathRecipient { + @Override + public void registerService(int listenerKey, NsdServiceInfo serviceInfo) { + mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage( + NsdManager.REGISTER_SERVICE, 0, listenerKey, + new ListenerArgs(this, serviceInfo))); + } + + @Override + public void unregisterService(int listenerKey) { + mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage( + NsdManager.UNREGISTER_SERVICE, 0, listenerKey, + new ListenerArgs(this, null))); + } + + @Override + public void discoverServices(int listenerKey, NsdServiceInfo serviceInfo) { + mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage( + NsdManager.DISCOVER_SERVICES, 0, listenerKey, + new ListenerArgs(this, serviceInfo))); + } + + @Override + public void stopDiscovery(int listenerKey) { + mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage( + NsdManager.STOP_DISCOVERY, 0, listenerKey, new ListenerArgs(this, null))); + } + + @Override + public void resolveService(int listenerKey, NsdServiceInfo serviceInfo) { + mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage( + NsdManager.RESOLVE_SERVICE, 0, listenerKey, + new ListenerArgs(this, serviceInfo))); + } + + @Override + public void startDaemon() { + mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage( + NsdManager.DAEMON_STARTUP, new ListenerArgs(this, null))); + } + + @Override + public void binderDied() { + mNsdStateMachine.sendMessage( + mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_CLIENT, this)); + } } private void notifyEnabled(boolean isEnabled) { @@ -832,43 +890,11 @@ public class NsdService extends INsdManager.Stub { mNsdStateMachine.dump(fd, pw, args); } - /* arg2 on the source message has an id that needs to be retained in replies - * see NsdManager for details */ - private Message obtainMessage(Message srcMsg) { - Message msg = Message.obtain(); - msg.arg2 = srcMsg.arg2; - return msg; - } - - private void replyToMessage(Message msg, int what) { - if (msg.replyTo == null) return; - Message dstMsg = obtainMessage(msg); - dstMsg.what = what; - mReplyChannel.replyToMessage(msg, dstMsg); - } - - private void replyToMessage(Message msg, int what, int arg1) { - if (msg.replyTo == null) return; - Message dstMsg = obtainMessage(msg); - dstMsg.what = what; - dstMsg.arg1 = arg1; - mReplyChannel.replyToMessage(msg, dstMsg); - } - - private void replyToMessage(Message msg, int what, Object obj) { - if (msg.replyTo == null) return; - Message dstMsg = obtainMessage(msg); - dstMsg.what = what; - dstMsg.obj = obj; - mReplyChannel.replyToMessage(msg, dstMsg); - } - /* Information tracked per client */ private class ClientInfo { private static final int MAX_LIMIT = 10; - private final AsyncChannel mChannel; - private final Messenger mMessenger; + private final INsdManagerCallback mCb; /* Remembers a resolved service until getaddrinfo completes */ private NsdServiceInfo mResolvedService; @@ -881,17 +907,14 @@ public class NsdService extends INsdManager.Stub { // The target SDK of this client < Build.VERSION_CODES.S private boolean mIsLegacy = false; - private ClientInfo(AsyncChannel c, Messenger m) { - mChannel = c; - mMessenger = m; - if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m); + private ClientInfo(INsdManagerCallback cb) { + mCb = cb; + if (DBG) Slog.d(TAG, "New client"); } @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("mChannel ").append(mChannel).append("\n"); - sb.append("mMessenger ").append(mMessenger).append("\n"); sb.append("mResolvedService ").append(mResolvedService).append("\n"); sb.append("mIsLegacy ").append(mIsLegacy).append("\n"); for(int i = 0; i< mClientIds.size(); i++) { @@ -949,6 +972,102 @@ public class NsdService extends INsdManager.Stub { } return mClientIds.keyAt(idx); } + + void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) { + try { + mCb.onDiscoverServicesStarted(listenerKey, info); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onDiscoverServicesStarted", e); + } + } + + void onDiscoverServicesFailed(int listenerKey, int error) { + try { + mCb.onDiscoverServicesFailed(listenerKey, error); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onDiscoverServicesFailed", e); + } + } + + void onServiceFound(int listenerKey, NsdServiceInfo info) { + try { + mCb.onServiceFound(listenerKey, info); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onServiceFound(", e); + } + } + + void onServiceLost(int listenerKey, NsdServiceInfo info) { + try { + mCb.onServiceLost(listenerKey, info); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onServiceLost(", e); + } + } + + void onStopDiscoveryFailed(int listenerKey, int error) { + try { + mCb.onStopDiscoveryFailed(listenerKey, error); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onStopDiscoveryFailed", e); + } + } + + void onStopDiscoverySucceeded(int listenerKey) { + try { + mCb.onStopDiscoverySucceeded(listenerKey); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onStopDiscoverySucceeded", e); + } + } + + void onRegisterServiceFailed(int listenerKey, int error) { + try { + mCb.onRegisterServiceFailed(listenerKey, error); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onRegisterServiceFailed", e); + } + } + + void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) { + try { + mCb.onRegisterServiceSucceeded(listenerKey, info); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onRegisterServiceSucceeded", e); + } + } + + void onUnregisterServiceFailed(int listenerKey, int error) { + try { + mCb.onUnregisterServiceFailed(listenerKey, error); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onUnregisterServiceFailed", e); + } + } + + void onUnregisterServiceSucceeded(int listenerKey) { + try { + mCb.onUnregisterServiceSucceeded(listenerKey); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onUnregisterServiceSucceeded", e); + } + } + + void onResolveServiceFailed(int listenerKey, int error) { + try { + mCb.onResolveServiceFailed(listenerKey, error); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onResolveServiceFailed", e); + } + } + + void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) { + try { + mCb.onResolveServiceSucceeded(listenerKey, info); + } catch (RemoteException e) { + Log.e(TAG, "Error calling onResolveServiceSucceeded", e); + } + } } /** diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index b019789e1982..ac20a08c1e30 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -685,6 +685,7 @@ public class Watchdog { final UUID errorId = mTraceErrorLogger.generateErrorId(); if (mTraceErrorLogger.isAddErrorIdEnabled()) { mTraceErrorLogger.addErrorIdToTrace("system_server", errorId); + mTraceErrorLogger.addSubjectToTrace(subject, errorId); } // Log the atom as early as possible since it is used as a mechanism to trigger diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ded115bbbcb4..02a16fc08c55 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -642,10 +642,11 @@ public class ActivityManagerService extends IActivityManager.Stub final BroadcastQueue mFgBroadcastQueue; final BroadcastQueue mBgBroadcastQueue; - final BroadcastQueue mOffloadBroadcastQueue; + final BroadcastQueue mBgOffloadBroadcastQueue; + final BroadcastQueue mFgOffloadBroadcastQueue; // Convenient for easy iteration over the queues. Foreground is first // so that dispatch of foreground broadcasts gets precedence. - final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[3]; + final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[4]; @GuardedBy("this") BroadcastStats mLastBroadcastStats; @@ -656,12 +657,20 @@ public class ActivityManagerService extends IActivityManager.Stub TraceErrorLogger mTraceErrorLogger; BroadcastQueue broadcastQueueForIntent(Intent intent) { - if (isOnOffloadQueue(intent.getFlags())) { + if (isOnFgOffloadQueue(intent.getFlags())) { if (DEBUG_BROADCAST_BACKGROUND) { Slog.i(TAG_BROADCAST, - "Broadcast intent " + intent + " on offload queue"); + "Broadcast intent " + intent + " on foreground offload queue"); } - return mOffloadBroadcastQueue; + return mFgOffloadBroadcastQueue; + } + + if (isOnBgOffloadQueue(intent.getFlags())) { + if (DEBUG_BROADCAST_BACKGROUND) { + Slog.i(TAG_BROADCAST, + "Broadcast intent " + intent + " on background offload queue"); + } + return mBgOffloadBroadcastQueue; } final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0; @@ -2263,7 +2272,8 @@ public class ActivityManagerService extends IActivityManager.Stub mPendingStartActivityUids = new PendingStartActivityUids(mContext); mUseFifoUiScheduling = false; mEnableOffloadQueue = false; - mFgBroadcastQueue = mBgBroadcastQueue = mOffloadBroadcastQueue = null; + mFgBroadcastQueue = mBgBroadcastQueue = mBgOffloadBroadcastQueue = + mFgOffloadBroadcastQueue = null; mComponentAliasResolver = new ComponentAliasResolver(this); } @@ -2324,11 +2334,14 @@ public class ActivityManagerService extends IActivityManager.Stub "foreground", foreConstants, false); mBgBroadcastQueue = new BroadcastQueue(this, mHandler, "background", backConstants, true); - mOffloadBroadcastQueue = new BroadcastQueue(this, mHandler, - "offload", offloadConstants, true); + mBgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler, + "offload_bg", offloadConstants, true); + mFgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler, + "offload_fg", foreConstants, true); mBroadcastQueues[0] = mFgBroadcastQueue; mBroadcastQueues[1] = mBgBroadcastQueue; - mBroadcastQueues[2] = mOffloadBroadcastQueue; + mBroadcastQueues[2] = mBgOffloadBroadcastQueue; + mBroadcastQueues[3] = mFgOffloadBroadcastQueue; mServices = new ActiveServices(this); mCpHelper = new ContentProviderHelper(this, true); @@ -12552,13 +12565,15 @@ public class ActivityManagerService extends IActivityManager.Stub boolean isPendingBroadcastProcessLocked(int pid) { return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid) || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid) - || mOffloadBroadcastQueue.isPendingBroadcastProcessLocked(pid); + || mBgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(pid) + || mFgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(pid); } boolean isPendingBroadcastProcessLocked(ProcessRecord app) { return mFgBroadcastQueue.isPendingBroadcastProcessLocked(app) || mBgBroadcastQueue.isPendingBroadcastProcessLocked(app) - || mOffloadBroadcastQueue.isPendingBroadcastProcessLocked(app); + || mBgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(app) + || mFgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(app); } void skipPendingBroadcastLocked(int pid) { @@ -12675,30 +12690,38 @@ public class ActivityManagerService extends IActivityManager.Stub "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED" + "flag"); } - if (CompatChanges.isChangeEnabled(DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, - callingUid) - && !explicitExportStateDefined) { - if (ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT) { - throw new SecurityException( - callerPackage + ": Targeting T+ (version " - + Build.VERSION_CODES.TIRAMISU - + " and above) requires that one of RECEIVER_EXPORTED or " - + "RECEIVER_NOT_EXPORTED be specified when registering a " - + "receiver"); - } else { - Slog.wtf(TAG, - callerPackage + ": Targeting T+ (version " - + Build.VERSION_CODES.TIRAMISU - + " and above) requires that one of RECEIVER_EXPORTED or " - + "RECEIVER_NOT_EXPORTED be specified when registering a " - + "receiver"); - // Assume default behavior-- flag check is not enforced + + // Don't enforce the flag check if we're EITHER registering for only protected + // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should + // not be used generally, so we will be marking them as exported by default + final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled( + DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid); + if (!onlyProtectedBroadcasts) { + if (receiver == null && !explicitExportStateDefined) { + // sticky broadcast, no flag specified (flag isn't required) + flags |= Context.RECEIVER_EXPORTED; + } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) { + if (ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT) { + throw new SecurityException( + callerPackage + ": Targeting T+ (version " + + Build.VERSION_CODES.TIRAMISU + + " and above) requires that one of RECEIVER_EXPORTED or " + + "RECEIVER_NOT_EXPORTED be specified when registering a " + + "receiver"); + } else { + Slog.wtf(TAG, + callerPackage + ": Targeting T+ (version " + + Build.VERSION_CODES.TIRAMISU + + " and above) requires that one of RECEIVER_EXPORTED or " + + "RECEIVER_NOT_EXPORTED be specified when registering a " + + "receiver"); + // Assume default behavior-- flag check is not enforced + flags |= Context.RECEIVER_EXPORTED; + } + } else if (!requireExplicitFlagForDynamicReceivers) { + // Change is not enabled, thus not targeting T+. Assume exported. flags |= Context.RECEIVER_EXPORTED; } - } else if (!CompatChanges.isChangeEnabled(DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, - callingUid)) { - // Change is not enabled, thus not targeting T+. Assume exported. - flags |= Context.RECEIVER_EXPORTED; } } @@ -12716,7 +12739,7 @@ public class ActivityManagerService extends IActivityManager.Stub (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) { continue; } - // If intent has scheme "content", it will need to acccess + // If intent has scheme "content", it will need to access // provider that needs to lock mProviderMap in ActivityThread // and also it may need to wait application response, so we // cannot lock ActivityManagerService here. @@ -14007,8 +14030,10 @@ public class ActivityManagerService extends IActivityManager.Stub BroadcastQueue queue; synchronized(this) { - if (isOnOffloadQueue(flags)) { - queue = mOffloadBroadcastQueue; + if (isOnFgOffloadQueue(flags)) { + queue = mFgOffloadBroadcastQueue; + } else if (isOnBgOffloadQueue(flags)) { + queue = mBgOffloadBroadcastQueue; } else { queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0 ? mFgBroadcastQueue : mBgBroadcastQueue; @@ -15392,6 +15417,16 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public String getSwitchingFromUserMessage() { + return mUserController.getSwitchingFromSystemUserMessage(); + } + + @Override + public String getSwitchingToUserMessage() { + return mUserController.getSwitchingToSystemUserMessage(); + } + + @Override public void setStopUserOnSwitch(@StopUserOnSwitch int value) { mUserController.setStopUserOnSwitch(value); } @@ -16302,7 +16337,7 @@ public class ActivityManagerService extends IActivityManager.Stub Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL); if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) { intent = new Intent(Intent.ACTION_LOCALE_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND + intent.addFlags(Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); if (initLocale || !mProcessesReady) { @@ -17479,7 +17514,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private boolean isOnOffloadQueue(int flags) { + private boolean isOnFgOffloadQueue(int flags) { + return ((flags & Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND) != 0); + } + + private boolean isOnBgOffloadQueue(int flags) { return (mEnableOffloadQueue && ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0)); } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 17daa753100c..592abbbb5747 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -789,7 +789,7 @@ public final class BroadcastQueue { // Ensure that broadcasts are only sent to other apps if they are explicitly marked as // exported, or are System level broadcasts - if (!skip && !filter.exported && Process.SYSTEM_UID != r.callingUid + if (!skip && !filter.exported && !Process.isCoreUid(r.callingUid) && filter.receiverList.uid != r.callingUid) { Slog.w(TAG, "Exported Denial: sending " @@ -800,7 +800,7 @@ public final class BroadcastQueue { + " due to receiver " + filter.receiverList.app + " (uid " + filter.receiverList.uid + ")" + " not specifying RECEIVER_EXPORTED"); - skip = true; + // skip = true; } if (skip) { diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 42205060e5d5..18ad1f557ee6 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -286,6 +286,7 @@ class ProcessErrorStateRecord { && mService.mTraceErrorLogger.isAddErrorIdEnabled()) { errorId = mService.mTraceErrorLogger.generateErrorId(); mService.mTraceErrorLogger.addErrorIdToTrace(mApp.processName, errorId); + mService.mTraceErrorLogger.addSubjectToTrace(annotation, errorId); } else { errorId = null; } diff --git a/services/core/java/com/android/server/am/TraceErrorLogger.java b/services/core/java/com/android/server/am/TraceErrorLogger.java index c65810097433..29a9b5c501c4 100644 --- a/services/core/java/com/android/server/am/TraceErrorLogger.java +++ b/services/core/java/com/android/server/am/TraceErrorLogger.java @@ -54,4 +54,17 @@ public class TraceErrorLogger { COUNTER_PREFIX + processName + "#" + errorId.toString(), PLACEHOLDER_VALUE); } + + /** + * Pushes a counter containing an ANR/Watchdog subject and a unique id so that the subject + * can be uniquely identified. + * + * @param subject The subject to include in the trace. + * @param errorId The unique id with which to tag the trace. + */ + public void addSubjectToTrace(String subject, UUID errorId) { + Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, + String.format("Subject(for ErrorId %s):%s", errorId.toString(), subject), + PLACEHOLDER_VALUE); + } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d52f52e59d39..0bab0235d718 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1809,7 +1809,8 @@ class UserController implements Handler.Callback { private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { // The dialog will show and then initiate the user switch by calling startUserInForeground mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second, - getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage()); + getSwitchingFromSystemUserMessageUnchecked(), + getSwitchingToSystemUserMessageUnchecked()); } private void dispatchForegroundProfileChanged(@UserIdInt int userId) { @@ -2622,18 +2623,40 @@ class UserController implements Handler.Callback { } } - private String getSwitchingFromSystemUserMessage() { + // Called by AMS, must check permission + String getSwitchingFromSystemUserMessage() { + checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()"); + + return getSwitchingFromSystemUserMessageUnchecked(); + } + + // Called by AMS, must check permission + String getSwitchingToSystemUserMessage() { + checkHasManageUsersPermission("getSwitchingToSystemUserMessage()"); + + return getSwitchingToSystemUserMessageUnchecked(); + } + + private String getSwitchingFromSystemUserMessageUnchecked() { synchronized (mLock) { return mSwitchingFromSystemUserMessage; } } - private String getSwitchingToSystemUserMessage() { + private String getSwitchingToSystemUserMessageUnchecked() { synchronized (mLock) { return mSwitchingToSystemUserMessage; } } + private void checkHasManageUsersPermission(String operation) { + if (mInjector.checkCallingPermission( + android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED) { + throw new SecurityException( + "You need MANAGE_USERS permission to call " + operation); + } + } + void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (mLock) { long token = proto.start(fieldId); @@ -2706,6 +2729,12 @@ class UserController implements Handler.Callback { pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); pw.println(" mInitialized:" + mInitialized); + if (mSwitchingFromSystemUserMessage != null) { + pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage); + } + if (mSwitchingToSystemUserMessage != null) { + pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage); + } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e4bed3d06714..1c62699f349c 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -110,6 +110,7 @@ import android.view.DisplayEventReceiver; import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; +import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -239,6 +240,10 @@ public final class DisplayManagerService extends SystemService { public final SparseArray<CallbackRecord> mCallbacks = new SparseArray<CallbackRecord>(); + /** All {@link DisplayWindowPolicyController}s indexed by {@link DisplayInfo#displayId}. */ + final SparseArray<DisplayWindowPolicyController> mDisplayWindowPolicyController = + new SparseArray<>(); + // List of all currently registered display adapters. private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>(); @@ -1117,7 +1122,8 @@ public final class DisplayManagerService extends SystemService { private int createVirtualDisplayInternal(IVirtualDisplayCallback callback, IMediaProjection projection, int callingUid, String packageName, Surface surface, - int flags, VirtualDisplayConfig virtualDisplayConfig) { + int flags, VirtualDisplayConfig virtualDisplayConfig, + DisplayWindowPolicyController controller) { synchronized (mSyncRoot) { if (mVirtualDisplayAdapter == null) { Slog.w(TAG, "Rejecting request to create private virtual display " @@ -1145,6 +1151,9 @@ public final class DisplayManagerService extends SystemService { final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); if (display != null) { + if (controller != null) { + mDisplayWindowPolicyController.put(display.getDisplayIdLocked(), controller); + } return display.getDisplayIdLocked(); } @@ -1188,6 +1197,10 @@ public final class DisplayManagerService extends SystemService { DisplayDevice device = mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken); if (device != null) { + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); + if (display != null) { + mDisplayWindowPolicyController.delete(display.getDisplayIdLocked()); + } // TODO: multi-display - handle virtual displays the same as other display adapters. mDisplayDeviceRepo.onDisplayDeviceEvent(device, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); @@ -2139,6 +2152,15 @@ public final class DisplayManagerService extends SystemService { } pw.println(); mPersistentDataStore.dump(pw); + + final int displayWindowPolicyControllerCount = mDisplayWindowPolicyController.size(); + pw.println(); + pw.println("Display Window Policy Controllers: size=" + + displayWindowPolicyControllerCount); + for (int i = 0; i < displayWindowPolicyControllerCount; i++) { + pw.print("Display " + mDisplayWindowPolicyController.keyAt(i) + ":"); + mDisplayWindowPolicyController.valueAt(i).dump(" ", pw); + } } pw.println(); mDisplayModeDirector.dump(pw); @@ -2704,6 +2726,13 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) { + return createVirtualDisplay(virtualDisplayConfig, callback, projection, packageName, + null /* controller */); + } + + public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, + IVirtualDisplayCallback callback, IMediaProjection projection, String packageName, + DisplayWindowPolicyController controller) { final int callingUid = Binder.getCallingUid(); if (!validatePackageName(callingUid, packageName)) { throw new SecurityException("packageName must match the calling uid"); @@ -2803,7 +2832,7 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { return createVirtualDisplayInternal(callback, projection, callingUid, packageName, - surface, flags, virtualDisplayConfig); + surface, flags, virtualDisplayConfig, controller); } finally { Binder.restoreCallingIdentity(token); } @@ -3624,6 +3653,13 @@ public final class DisplayManagerService extends SystemService { public void onEarlyInteractivityChange(boolean interactive) { mLogicalDisplayMapper.onEarlyInteractivityChange(interactive); } + + @Override + public DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId) { + synchronized (mSyncRoot) { + return mDisplayWindowPolicyController.get(displayId); + } + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index ffeaad173fca..457c2fd6fc41 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -154,10 +154,10 @@ public class LocaleManagerService extends SystemService { final ActivityTaskManagerInternal.PackageConfigurationUpdater updater = mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName, userId); - boolean isSuccess = updater.setLocales(locales).commit(); + boolean isConfigChanged = updater.setLocales(locales).commit(); //We want to send the broadcasts only if config was actually updated on commit. - if (isSuccess) { + if (isConfigChanged) { notifyAppWhoseLocaleChanged(appPackageName, userId, locales); notifyInstallerOfAppWhoseLocaleChanged(appPackageName, userId, locales); notifyRegisteredReceivers(appPackageName, userId, locales); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bde554323a73..e117cc6f7a22 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6822,13 +6822,11 @@ public class NotificationManagerService extends SystemService { // blocked apps - boolean isMediaNotification = n.isMediaNotification() - && n.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null; boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid); synchronized (mNotificationLock) { isBlocked |= isRecordBlockedLocked(r); } - if (isBlocked && !isMediaNotification) { + if (isBlocked && !n.isMediaNotification()) { if (DBG) { Slog.e(TAG, "Suppressing notification from package " + r.getSbn().getPackageName() + " by user request."); @@ -7217,10 +7215,8 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification n = r.getSbn(); final Notification notification = n.getNotification(); - boolean isMediaNotification = notification.isMediaNotification() - && notification.extras.getParcelable( - Notification.EXTRA_MEDIA_SESSION) != null; - if (!isMediaNotification && (appBanned || isRecordBlockedLocked(r))) { + if (!notification.isMediaNotification() + && (appBanned || isRecordBlockedLocked(r))) { mUsageStats.registerBlocked(r); if (DBG) { Slog.e(TAG, "Suppressing notification from package " + pkg); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 39ed9c2d51d3..6622a778ec44 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2050,6 +2050,7 @@ final class InstallPackageHelper { } } + final String packageName = pkg.getPackageName(); for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) { final String filePath = entry.getKey(); final String signaturePath = entry.getValue(); @@ -2077,10 +2078,13 @@ final class InstallPackageHelper { try { // A file may already have fs-verity, e.g. when reused during a split // install. If the measurement succeeds, no need to attempt to set up. - mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash); + mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath, + rootHash); } catch (Installer.InstallerException e) { - mPm.mInstaller.installApkVerity(filePath, fd, result.getContentSize()); - mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash); + mPm.mInstaller.installApkVerity(packageName, filePath, fd, + result.getContentSize()); + mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath, + rootHash); } } finally { IoUtils.closeQuietly(fd); @@ -2349,10 +2353,9 @@ final class InstallPackageHelper { // Set install reason for users that are having the package newly installed. final int[] allUsersList = mPm.mUserManager.getUserIds(); if (userId == UserHandle.USER_ALL) { - // TODO(b/152629990): It appears that the package doesn't actually get newly - // installed in this case, so the installReason shouldn't get modified? for (int currentUserId : allUsersList) { - if (!previousUserIds.contains(currentUserId)) { + if (!previousUserIds.contains(currentUserId) + && ps.getInstalled(currentUserId)) { ps.setInstallReason(installReason, currentUserId); } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index c8bd2c0bcecf..a3803447083a 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -497,7 +497,7 @@ public class Installer extends SystemService { * * @throws InstallerException if {@code dexopt} fails. */ - public boolean dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, + public boolean dexopt(String apkPath, int uid, String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, @Nullable String classLoaderContext, @Nullable String seInfo, boolean downgrade, int targetSdkVersion, @@ -585,11 +585,14 @@ public class Installer extends SystemService { } } - public void rmPackageDir(String packageDir) throws InstallerException { + /** + * Remove a directory belonging to a package. + */ + public void rmPackageDir(String packageName, String packageDir) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(packageDir); try { - mInstalld.rmPackageDir(packageDir); + mInstalld.rmPackageDir(packageName, packageDir); } catch (Exception e) { throw InstallerException.from(e); } @@ -662,35 +665,44 @@ public class Installer extends SystemService { } } - public void createOatDir(String oatDir, String dexInstructionSet) + /** + * Creates an oat dir for given package and instruction set. + */ + public void createOatDir(String packageName, String oatDir, String dexInstructionSet) throws InstallerException { if (!checkBeforeRemote()) return; try { - mInstalld.createOatDir(oatDir, dexInstructionSet); + mInstalld.createOatDir(packageName, oatDir, dexInstructionSet); } catch (Exception e) { throw InstallerException.from(e); } } - public void linkFile(String relativePath, String fromBase, String toBase) + /** + * Creates a hardlink for a path. + */ + public void linkFile(String packageName, String relativePath, String fromBase, String toBase) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(fromBase); BlockGuard.getVmPolicy().onPathAccess(toBase); try { - mInstalld.linkFile(relativePath, fromBase, toBase); + mInstalld.linkFile(packageName, relativePath, fromBase, toBase); } catch (Exception e) { throw InstallerException.from(e); } } - public void moveAb(String apkPath, String instructionSet, String outputPath) + /** + * Moves oat/vdex/art from "B" set defined by ro.boot.slot_suffix to the default set. + */ + public void moveAb(String packageName, String apkPath, String instructionSet, String outputPath) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(apkPath); BlockGuard.getVmPolicy().onPathAccess(outputPath); try { - mInstalld.moveAb(apkPath, instructionSet, outputPath); + mInstalld.moveAb(packageName, apkPath, instructionSet, outputPath); } catch (Exception e) { throw InstallerException.from(e); } @@ -700,35 +712,41 @@ public class Installer extends SystemService { * Deletes the optimized artifacts generated by ART and returns the number * of freed bytes. */ - public long deleteOdex(String apkPath, String instructionSet, String outputPath) - throws InstallerException { + public long deleteOdex(String packageName, String apkPath, String instructionSet, + String outputPath) throws InstallerException { if (!checkBeforeRemote()) return -1; BlockGuard.getVmPolicy().onPathAccess(apkPath); BlockGuard.getVmPolicy().onPathAccess(outputPath); try { - return mInstalld.deleteOdex(apkPath, instructionSet, outputPath); + return mInstalld.deleteOdex(packageName, apkPath, instructionSet, outputPath); } catch (Exception e) { throw InstallerException.from(e); } } - public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize) - throws InstallerException { + /** + * Enables legacy apk-verity for an apk. + */ + public void installApkVerity(String packageName, String filePath, FileDescriptor verityInput, + int contentSize) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(filePath); try { - mInstalld.installApkVerity(filePath, verityInput, contentSize); + mInstalld.installApkVerity(packageName, filePath, verityInput, contentSize); } catch (Exception e) { throw InstallerException.from(e); } } - public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash) - throws InstallerException { + /** + * Checks if provided hash matches the file's fs-verity merkle tree root hash. + */ + public void assertFsverityRootHashMatches(String packageName, String filePath, + @NonNull byte[] expectedHash) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(filePath); try { - mInstalld.assertFsverityRootHashMatches(filePath, expectedHash); + mInstalld.assertFsverityRootHashMatches(packageName, filePath, expectedHash); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 9e6f4f7b75d2..c125fe10e899 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -411,6 +411,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { final List<String> paths = AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); + final String packageName = pkg.getPackageName(); for (String dexCodeInstructionSet : dexCodeInstructionSets) { for (String path : paths) { String oatDir = PackageDexOptimizer.getOatDir( @@ -420,7 +421,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { packagePaths++; try { - installer.moveAb(path, dexCodeInstructionSet, oatDir); + installer.moveAb(packageName, path, dexCodeInstructionSet, oatDir); pathsSuccessful++; } catch (InstallerException e) { } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 803a2833dc3c..8f14cf8787f3 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2402,6 +2402,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { final List<File> fromFiles = mResolvedInheritedFiles; final File toDir = stageDir; + final String tempPackageName = toDir.getName(); if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { @@ -2411,7 +2412,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (isLinkPossible(fromFiles, toDir)) { if (!mResolvedInstructionSets.isEmpty()) { final File oatDir = new File(toDir, "oat"); - createOatDirs(mResolvedInstructionSets, oatDir); + createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir); } // pre-create lib dirs for linking if necessary if (!mResolvedNativeLibPaths.isEmpty()) { @@ -2434,7 +2435,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { new File(libDir, archDirPath)); } } - linkFiles(fromFiles, toDir, mInheritedFilesBase); + linkFiles(tempPackageName, fromFiles, toDir, mInheritedFilesBase); } else { // TODO: this should delegate to DCS so the system process // avoids holding open FDs into containers. @@ -3529,18 +3530,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new IOException("File: " + pathStr + " outside base: " + baseStr); } - private void createOatDirs(List<String> instructionSets, File fromDir) + private void createOatDirs(String packageName, List<String> instructionSets, File fromDir) throws PackageManagerException { for (String instructionSet : instructionSets) { try { - mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet); + mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet); } catch (InstallerException e) { throw PackageManagerException.from(e); } } } - private void linkFile(String relativePath, String fromBase, String toBase) throws IOException { + private void linkFile(String packageName, String relativePath, String fromBase, String toBase) + throws IOException { try { // Try final IncrementalFileStorages incrementalFileStorages = getIncrementalFileStorages(); @@ -3548,21 +3550,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { fromBase, toBase)) { return; } - mInstaller.linkFile(relativePath, fromBase, toBase); + mInstaller.linkFile(packageName, relativePath, fromBase, toBase); } catch (InstallerException | IOException e) { throw new IOException("failed linkOrCreateDir(" + relativePath + ", " + fromBase + ", " + toBase + ")", e); } } - private void linkFiles(List<File> fromFiles, File toDir, File fromDir) + private void linkFiles(String packageName, List<File> fromFiles, File toDir, File fromDir) throws IOException { for (File fromFile : fromFiles) { final String relativePath = getRelativePath(fromFile, fromDir); final String fromBase = fromDir.getAbsolutePath(); final String toBase = toDir.getAbsolutePath(); - linkFile(relativePath, fromBase, toBase); + linkFile(packageName, relativePath, fromBase, toBase); } Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); @@ -4299,7 +4301,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { incrementalFileStorages.cleanUpAndMarkComplete(); } if (stageDir != null) { - mInstaller.rmPackageDir(stageDir.getAbsolutePath()); + final String tempPackageName = stageDir.getName(); + mInstaller.rmPackageDir(tempPackageName, stageDir.getAbsolutePath()); } } catch (InstallerException ignored) { } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 27288b9f5343..747bbfa57bd1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1500,16 +1500,19 @@ public class PackageManagerService extends IPackageManager.Stub final CompatChange.ChangeListener selinuxChangeListener = packageName -> { synchronized (m.mInstallLock) { final AndroidPackage pkg; + final PackageSetting ps; final SharedUserSetting sharedUser; final String oldSeInfo; - final PackageStateInternal packageState = m.getPackageStateInternal(packageName); - if (packageState == null) { - Slog.e(TAG, "Failed to find package setting " + packageName); - return; + synchronized (m.mLock) { + ps = m.mSettings.getPackageLPr(packageName); + if (ps == null) { + Slog.e(TAG, "Failed to find package setting " + packageName); + return; + } + pkg = ps.getPkg(); + sharedUser = ps.getSharedUser(); + oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps); } - pkg = packageState.getPkg(); - sharedUser = packageState.getSharedUser(); - oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState); if (pkg == null) { Slog.e(TAG, "Failed to find package " + packageName); @@ -1521,7 +1524,7 @@ public class PackageManagerService extends IPackageManager.Stub if (!newSeInfo.equals(oldSeInfo)) { Slog.i(TAG, "Updating seInfo for package " + packageName + " from: " + oldSeInfo + " to: " + newSeInfo); - packageState.getTransientState().setOverrideSeInfo(newSeInfo); + ps.getPkgState().setOverrideSeInfo(newSeInfo); m.mAppDataHelper.prepareAppDataAfterInstallLIF(pkg); } } @@ -6848,24 +6851,24 @@ public class PackageManagerService extends IPackageManager.Stub } enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, true /* checkShell */, "stop package"); - boolean shouldUnhibernate = false; // writer synchronized (mLock) { final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null && ps.getStopped(userId) && !stopped) { - shouldUnhibernate = true; - } if (!shouldFilterApplication(ps, callingUid, userId) && mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) { scheduleWritePackageRestrictionsLocked(userId); } } - if (shouldUnhibernate) { + // If this would cause the app to leave force-stop, then also make sure to unhibernate the + // app if needed. + if (!stopped) { mHandler.post(() -> { AppHibernationManagerInternal ah = mInjector.getLocalService(AppHibernationManagerInternal.class); - ah.setHibernatingForUser(packageName, userId, false); - ah.setHibernatingGlobally(packageName, false); + if (ah != null && ah.isHibernatingForUser(packageName, userId)) { + ah.setHibernatingForUser(packageName, userId, false); + ah.setHibernatingGlobally(packageName, false); + } }); } } diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 3a2ac1c9b6df..48b893bda546 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -94,9 +94,10 @@ final class RemovePackageHelper { } } - mInstaller.rmPackageDir(codePath.getAbsolutePath()); + final String packageName = codePath.getName(); + mInstaller.rmPackageDir(packageName, codePath.getAbsolutePath()); if (needRemoveParent) { - mInstaller.rmPackageDir(codePathParent.getAbsolutePath()); + mInstaller.rmPackageDir(packageName, codePathParent.getAbsolutePath()); removeCachedResult(codePathParent); } } catch (Installer.InstallerException e) { diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java index 6cc94ce1f657..9b08ef9b3525 100644 --- a/services/core/java/com/android/server/pm/ScanPackageHelper.java +++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java @@ -894,14 +894,15 @@ final class ScanPackageHelper { * Returns if forced apk verification can be skipped for the whole package, including splits. */ private boolean canSkipForcedPackageVerification(AndroidPackage pkg) { - if (!canSkipForcedApkVerification(pkg.getBaseApkPath())) { + final String packageName = pkg.getPackageName(); + if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) { return false; } // TODO: Allow base and splits to be verified individually. String[] splitCodePaths = pkg.getSplitCodePaths(); if (!ArrayUtils.isEmpty(splitCodePaths)) { for (int i = 0; i < splitCodePaths.length; i++) { - if (!canSkipForcedApkVerification(splitCodePaths[i])) { + if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) { return false; } } @@ -914,7 +915,7 @@ final class ScanPackageHelper { * whether the apk contains signed root hash. Note that the signer's certificate still needs to * match one in a trusted source, and should be done separately. */ - private boolean canSkipForcedApkVerification(String apkPath) { + private boolean canSkipForcedApkVerification(String packageName, String apkPath) { if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) { return VerityUtils.hasFsverity(apkPath); } @@ -926,7 +927,8 @@ final class ScanPackageHelper { } synchronized (mPm.mInstallLock) { // Returns whether the observed root hash matches what kernel has. - mPm.mInstaller.assertFsverityRootHashMatches(apkPath, rootHashObserved); + mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath, + rootHashObserved); return true; } } catch (Installer.InstallerException | IOException | DigestException diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 302826ff1294..40d884598ceb 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -20,6 +20,8 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountManager; import android.annotation.ColorRes; import android.annotation.DrawableRes; import android.annotation.NonNull; @@ -85,6 +87,7 @@ import android.provider.Settings; import android.security.GateKeeper; import android.service.gatekeeper.IGateKeeperService; import android.stats.devicepolicy.DevicePolicyEnums; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -3512,6 +3515,39 @@ public class UserManagerService extends IUserManager.Stub { } } + @Override + public UserHandle createUserWithAttributes( + String userName, String userType, @UserInfoFlag int flags, + Bitmap userIcon, + String accountName, String accountType, PersistableBundle accountOptions) { + checkManageOrCreateUsersPermission(flags); + + if (someUserHasAccountNoChecks(accountName, accountType)) { + throw new ServiceSpecificException( + UserManager.USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS); + } + + UserInfo userInfo; + try { + userInfo = createUserInternal(userName, userType, flags, + UserHandle.USER_NULL, null); + + if (userInfo == null) { + throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN); + } + } catch (UserManager.CheckedUserOperationException e) { + throw e.toServiceSpecificException(); + } + + if (userIcon != null) { + mLocalService.setUserIcon(userInfo.id, userIcon); + } + + setSeedAccountDataNoChecks(userInfo.id, accountName, accountType, accountOptions, true); + + return userInfo.getUserHandle(); + } + private UserInfo createUserInternal(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, @Nullable String[] disallowedPackages) @@ -4934,7 +4970,12 @@ public class UserManagerService extends IUserManager.Stub { @Override public void setSeedAccountData(@UserIdInt int userId, String accountName, String accountType, PersistableBundle accountOptions, boolean persist) { - checkManageUsersPermission("Require MANAGE_USERS permission to set user seed data"); + checkManageUsersPermission("set user seed account data"); + setSeedAccountDataNoChecks(userId, accountName, accountType, accountOptions, persist); + } + + private void setSeedAccountDataNoChecks(@UserIdInt int userId, String accountName, + String accountType, PersistableBundle accountOptions, boolean persist) { synchronized (mPackagesLock) { final UserData userData; synchronized (mUsersLock) { @@ -4996,14 +5037,18 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public boolean someUserHasSeedAccount(String accountName, String accountType) - throws RemoteException { - checkManageUsersPermission("Cannot check seed account information"); + public boolean someUserHasSeedAccount(String accountName, String accountType) { + checkManageUsersPermission("check seed account information"); + return someUserHasSeedAccountNoChecks(accountName, accountType); + } + + private boolean someUserHasSeedAccountNoChecks(String accountName, String accountType) { synchronized (mUsersLock) { final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { final UserData data = mUsers.valueAt(i); if (data.info.isInitialized()) continue; + if (mRemovingUserIds.get(data.info.id)) continue; if (data.seedAccountName == null || !data.seedAccountName.equals(accountName)) { continue; } @@ -5017,6 +5062,25 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean someUserHasAccount(String accountName, String accountType) { + checkManageOrCreateUsersPermission("check seed account information"); + return someUserHasAccountNoChecks(accountName, accountType); + } + + private boolean someUserHasAccountNoChecks( + String accountName, String accountType) { + if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) { + return false; + } + + final Account account = new Account(accountName, accountType); + + return Binder.withCleanCallingIdentity(() -> + AccountManager.get(mContext).someUserHasAccount(account) + || someUserHasSeedAccountNoChecks(accountName, accountType)); + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 58204891293c..5371454db43c 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -1043,10 +1043,12 @@ public class DexManager { public long deleteOptimizedFiles(ArtPackageInfo packageInfo) { long freedBytes = 0; boolean hadErrors = false; + final String packageName = packageInfo.getPackageName(); for (String codePath : packageInfo.getCodePaths()) { for (String isa : packageInfo.getInstructionSets()) { try { - freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir()); + freedBytes += mInstaller.deleteOdex(packageName, codePath, isa, + packageInfo.getOatDir()); } catch (InstallerException e) { Log.e(TAG, "Failed deleting oat files for " + codePath, e); hadErrors = true; diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index ece0a62b7877..e207ff1d9092 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -702,6 +702,14 @@ final class DefaultPermissionGrantPolicy { DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, userId), userId, CONTACTS_PERMISSIONS); + // Maps + if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) { + grantPermissionsToSystemPackage(pm, + getDefaultSystemHandlerActivityPackageForCategory(pm, + Intent.CATEGORY_APP_MAPS, userId), + userId, FOREGROUND_LOCATION_PERMISSIONS); + } + // Email grantPermissionsToSystemPackage(pm, getDefaultSystemHandlerActivityPackageForCategory(pm, diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2369c5e984f2..131e5873a5a2 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -332,6 +332,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800; private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); + private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); /** * Keyguard stuff @@ -1115,21 +1119,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case LONG_PRESS_POWER_GLOBAL_ACTIONS: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Long Press - Global Actions"); showGlobalActions(); break; case LONG_PRESS_POWER_SHUT_OFF: case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Long Press - Shut Off"); sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF); break; case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Long Press - Go To Voice Assist"); // Some devices allow the voice assistant intent during setup (and use that intent // to launch something else, like Settings). So we explicitly allow that via the @@ -1153,7 +1157,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Very Long Press - Show Global Actions"); showGlobalActions(); break; @@ -2098,7 +2102,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyHandled = true; break; case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS: - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback( + HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power + Volume Up - Global Actions"); showGlobalActions(); mPowerKeyHandled = true; @@ -5291,7 +5296,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { return false; } - mVibrator.vibrate(uid, packageName, effect, reason, TOUCH_VIBRATION_ATTRIBUTES); + mVibrator.vibrate(uid, packageName, effect, reason, getVibrationAttributes(effectId)); return true; } @@ -5320,6 +5325,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case HapticFeedbackConstants.GESTURE_START: return VibrationEffect.get(VibrationEffect.EFFECT_CLICK); case HapticFeedbackConstants.LONG_PRESS: + case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: case HapticFeedbackConstants.EDGE_SQUEEZE: return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); case HapticFeedbackConstants.REJECT: @@ -5358,6 +5364,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private VibrationAttributes getVibrationAttributes(int effectId) { + switch (effectId) { + case HapticFeedbackConstants.EDGE_SQUEEZE: + case HapticFeedbackConstants.EDGE_RELEASE: + return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES; + case HapticFeedbackConstants.ASSISTANT_BUTTON: + case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: + case HapticFeedbackConstants.ROTARY_SCROLL_TICK: + case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS: + case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT: + return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; + default: + return TOUCH_VIBRATION_ATTRIBUTES; + } + } + @Override public void keepScreenOnStartedLw() { } diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 6d0f08de64de..70a804b8135b 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -110,8 +110,8 @@ public class Notifier { private static final VibrationEffect CHARGING_VIBRATION_EFFECT = VibrationEffect.createWaveform(CHARGING_VIBRATION_TIME, CHARGING_VIBRATION_AMPLITUDE, -1); - private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); private final Object mLock = new Object(); @@ -807,7 +807,7 @@ public class Notifier { final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0; if (vibrate) { - mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, TOUCH_VIBRATION_ATTRIBUTES); + mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); } // play sound diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e9d5ad6f6d37..e69acc3071fb 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -470,14 +470,16 @@ public final class TvInputManagerService extends SystemService { } private void stopUser(int userId) { - if (userId == mCurrentUserId) { - switchUser(ActivityManager.getCurrentUser()); - return; - } + synchronized (mLock) { + if (userId == mCurrentUserId) { + switchUser(ActivityManager.getCurrentUser()); + return; + } - releaseSessionOfUserLocked(userId); - unbindServiceOfUserLocked(userId); - mRunningProfiles.remove(userId); + releaseSessionOfUserLocked(userId); + unbindServiceOfUserLocked(userId); + mRunningProfiles.remove(userId); + } } private void startProfileLocked(int userId) { diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java index 4d1ff9eb3f81..dfb07523d830 100644 --- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java @@ -27,6 +27,7 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppManager; import android.media.tv.interactive.ITvIAppManagerCallback; @@ -42,6 +43,7 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; @@ -58,10 +60,12 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; + /** * This class provides a system service that manages interactive TV applications. */ @@ -81,6 +85,8 @@ public class TvIAppManagerService extends SystemService { @GuardedBy("mLock") private final SparseArray<UserState> mUserStates = new SparseArray<>(); + private final UserManager mUserManager; + /** * Initializes the system service. * <p> @@ -93,6 +99,7 @@ public class TvIAppManagerService extends SystemService { public TvIAppManagerService(Context context) { super(context); mContext = context; + mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); } @GuardedBy("mLock") @@ -330,11 +337,188 @@ public class TvIAppManagerService extends SystemService { mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - // TODO: handle switch / start / stop user + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_USER_STARTED.equals(action)) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + startUser(userId); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + stopUser(userId); + } } }, UserHandle.ALL, intentFilter, null, null); } + private void switchUser(int userId) { + synchronized (mLock) { + if (mCurrentUserId == userId) { + return; + } + UserInfo userInfo = mUserManager.getUserInfo(userId); + if (userInfo.isProfile()) { + Slog.w(TAG, "cannot switch to a profile!"); + return; + } + + for (int runningId : mRunningProfiles) { + releaseSessionOfUserLocked(runningId); + unbindServiceOfUserLocked(runningId); + } + mRunningProfiles.clear(); + releaseSessionOfUserLocked(mCurrentUserId); + unbindServiceOfUserLocked(mCurrentUserId); + + mCurrentUserId = userId; + buildTvIAppServiceListLocked(userId, null); + } + } + + private void removeUser(int userId) { + synchronized (mLock) { + UserState userState = getUserStateLocked(userId); + if (userState == null) { + return; + } + // Release all created sessions. + for (SessionState state : userState.mSessionStateMap.values()) { + if (state.mSession != null) { + try { + state.mSession.release(); + } catch (RemoteException e) { + Slog.e(TAG, "error in release", e); + } + } + } + userState.mSessionStateMap.clear(); + + // Unregister all callbacks and unbind all services. + for (ServiceState serviceState : userState.mServiceStateMap.values()) { + if (serviceState.mService != null) { + if (serviceState.mCallback != null) { + try { + serviceState.mService.unregisterCallback(serviceState.mCallback); + } catch (RemoteException e) { + Slog.e(TAG, "error in unregisterCallback", e); + } + } + mContext.unbindService(serviceState.mConnection); + } + } + userState.mServiceStateMap.clear(); + + // Clear everything else. + userState.mIAppMap.clear(); + userState.mPackageSet.clear(); + userState.mClientStateMap.clear(); + userState.mCallbacks.kill(); + + mRunningProfiles.remove(userId); + mUserStates.remove(userId); + + if (userId == mCurrentUserId) { + switchUser(UserHandle.USER_SYSTEM); + } + } + } + + private void startUser(int userId) { + synchronized (mLock) { + if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) { + // user already started + return; + } + UserInfo userInfo = mUserManager.getUserInfo(userId); + UserInfo parentInfo = mUserManager.getProfileParent(userId); + if (userInfo.isProfile() + && parentInfo != null + && parentInfo.id == mCurrentUserId) { + // only the children of the current user can be started in background + startProfileLocked(userId); + } + } + } + + private void stopUser(int userId) { + synchronized (mLock) { + if (userId == mCurrentUserId) { + switchUser(ActivityManager.getCurrentUser()); + return; + } + + releaseSessionOfUserLocked(userId); + unbindServiceOfUserLocked(userId); + mRunningProfiles.remove(userId); + } + } + + @GuardedBy("mLock") + private void startProfileLocked(int userId) { + mRunningProfiles.add(userId); + buildTvIAppServiceListLocked(userId, null); + } + + @GuardedBy("mLock") + private void releaseSessionOfUserLocked(int userId) { + UserState userState = getUserStateLocked(userId); + if (userState == null) { + return; + } + List<SessionState> sessionStatesToRelease = new ArrayList<>(); + for (SessionState sessionState : userState.mSessionStateMap.values()) { + if (sessionState.mSession != null) { + sessionStatesToRelease.add(sessionState); + } + } + for (SessionState sessionState : sessionStatesToRelease) { + try { + sessionState.mSession.release(); + } catch (RemoteException e) { + Slog.e(TAG, "error in release", e); + } + clearSessionAndNotifyClientLocked(sessionState); + } + } + + @GuardedBy("mLock") + private void unbindServiceOfUserLocked(int userId) { + UserState userState = getUserStateLocked(userId); + if (userState == null) { + return; + } + for (Iterator<ComponentName> it = userState.mServiceStateMap.keySet().iterator(); + it.hasNext(); ) { + ComponentName component = it.next(); + ServiceState serviceState = userState.mServiceStateMap.get(component); + if (serviceState != null && serviceState.mSessionTokens.isEmpty()) { + if (serviceState.mCallback != null) { + try { + serviceState.mService.unregisterCallback(serviceState.mCallback); + } catch (RemoteException e) { + Slog.e(TAG, "error in unregisterCallback", e); + } + } + mContext.unbindService(serviceState.mConnection); + it.remove(); + } + } + } + + @GuardedBy("mLock") + private void clearSessionAndNotifyClientLocked(SessionState state) { + if (state.mClient != null) { + try { + state.mClient.onSessionReleased(state.mSeq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onSessionReleased", e); + } + } + removeSessionStateLocked(state.mSessionToken, state.mUserId); + } + private SessionState getSessionState(IBinder sessionToken) { // TODO: implement user state and get session from it. return null; diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index bc21f1bd2ed3..71a6b2265075 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -16,6 +16,14 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.USAGE_ALARM; +import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST; +import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK; +import static android.os.VibrationAttributes.USAGE_NOTIFICATION; +import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION; +import static android.os.VibrationAttributes.USAGE_RINGTONE; +import static android.os.VibrationAttributes.USAGE_TOUCH; + import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IUidObserver; @@ -90,6 +98,8 @@ final class VibrationSettings { @GuardedBy("mLock") private int mHapticFeedbackIntensity; @GuardedBy("mLock") + private int mHardwareFeedbackIntensity; + @GuardedBy("mLock") private int mNotificationIntensity; @GuardedBy("mLock") private int mRingIntensity; @@ -232,17 +242,20 @@ final class VibrationSettings { * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_* */ public int getDefaultIntensity(int usageHint) { - if (isAlarm(usageHint)) { + if (usageHint == USAGE_ALARM) { return Vibrator.VIBRATION_INTENSITY_HIGH; } synchronized (mLock) { if (mVibrator != null) { - if (isRingtone(usageHint)) { - return mVibrator.getDefaultRingVibrationIntensity(); - } else if (isNotification(usageHint)) { - return mVibrator.getDefaultNotificationVibrationIntensity(); - } else if (isHapticFeedback(usageHint)) { - return mVibrator.getDefaultHapticFeedbackIntensity(); + switch (usageHint) { + case USAGE_RINGTONE: + return mVibrator.getDefaultRingVibrationIntensity(); + case USAGE_NOTIFICATION: + return mVibrator.getDefaultNotificationVibrationIntensity(); + case USAGE_TOUCH: + case USAGE_HARDWARE_FEEDBACK: + case USAGE_PHYSICAL_EMULATION: + return mVibrator.getDefaultHapticFeedbackIntensity(); } } } @@ -257,16 +270,20 @@ final class VibrationSettings { */ public int getCurrentIntensity(int usageHint) { synchronized (mLock) { - if (isRingtone(usageHint)) { - return mRingIntensity; - } else if (isNotification(usageHint)) { - return mNotificationIntensity; - } else if (isHapticFeedback(usageHint)) { - return mHapticFeedbackIntensity; - } else if (isAlarm(usageHint)) { - return Vibrator.VIBRATION_INTENSITY_HIGH; - } else { - return Vibrator.VIBRATION_INTENSITY_MEDIUM; + switch (usageHint) { + case USAGE_RINGTONE: + return mRingIntensity; + case USAGE_NOTIFICATION: + return mNotificationIntensity; + case USAGE_TOUCH: + return mHapticFeedbackIntensity; + case USAGE_HARDWARE_FEEDBACK: + case USAGE_PHYSICAL_EMULATION: + return mHardwareFeedbackIntensity; + case USAGE_ALARM: + return Vibrator.VIBRATION_INTENSITY_HIGH; + default: + return Vibrator.VIBRATION_INTENSITY_MEDIUM; } } } @@ -289,7 +306,7 @@ final class VibrationSettings { * for ringtone usage only. All other usages are allowed independently of ringer mode. */ public boolean shouldVibrateForRingerMode(int usageHint) { - if (!isRingtone(usageHint)) { + if (usageHint != USAGE_RINGTONE) { return true; } synchronized (mLock) { @@ -324,8 +341,10 @@ final class VibrationSettings { * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST} usages are allowed to vibrate. */ public boolean shouldVibrateForPowerMode(int usageHint) { - return !mLowPowerMode || isRingtone(usageHint) || isAlarm(usageHint) - || usageHint == VibrationAttributes.USAGE_COMMUNICATION_REQUEST; + synchronized (mLock) { + return !mLowPowerMode || usageHint == USAGE_RINGTONE || usageHint == USAGE_ALARM + || usageHint == USAGE_COMMUNICATION_REQUEST; + } } /** Return {@code true} if input devices should vibrate instead of this device. */ @@ -338,22 +357,6 @@ final class VibrationSettings { return mZenMode != Settings.Global.ZEN_MODE_OFF; } - private static boolean isNotification(int usageHint) { - return usageHint == VibrationAttributes.USAGE_NOTIFICATION; - } - - private static boolean isRingtone(int usageHint) { - return usageHint == VibrationAttributes.USAGE_RINGTONE; - } - - private static boolean isHapticFeedback(int usageHint) { - return usageHint == VibrationAttributes.USAGE_TOUCH; - } - - private static boolean isAlarm(int usageHint) { - return usageHint == VibrationAttributes.USAGE_ALARM; - } - private static boolean isClassAlarm(int usageHint) { return (usageHint & VibrationAttributes.USAGE_CLASS_MASK) == VibrationAttributes.USAGE_CLASS_ALARM; @@ -365,18 +368,35 @@ final class VibrationSettings { mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0; mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0; mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); + getDefaultIntensity(USAGE_TOUCH)); + mHardwareFeedbackIntensity = getSystemSetting( + Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, + getHardwareFeedbackIntensityWhenSettingIsMissing(mHapticFeedbackIntensity)); mNotificationIntensity = getSystemSetting( Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); + getDefaultIntensity(USAGE_NOTIFICATION)); mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + getDefaultIntensity(USAGE_RINGTONE)); mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); } notifyListeners(); } + /** + * Return the value to be used for {@link Settings.System#HARDWARE_HAPTIC_FEEDBACK_INTENSITY} + * when the value was not set by the user. + * + * <p>This should adapt the behavior preceding the introduction of this new setting key, which + * is to apply {@link Settings.System#HAPTIC_FEEDBACK_INTENSITY} unless it's disabled. + */ + private int getHardwareFeedbackIntensityWhenSettingIsMissing(int hapticFeedbackIntensity) { + if (hapticFeedbackIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + return getDefaultIntensity(USAGE_HARDWARE_FEEDBACK); + } + return hapticFeedbackIntensity; + } + @Override public String toString() { return "VibrationSettings{" @@ -389,18 +409,20 @@ final class VibrationSettings { + ", mHapticChannelMaxVibrationAmplitude=" + getHapticChannelMaxVibrationAmplitude() + ", mRampStepDuration=" + mRampStepDuration + ", mRampDownDuration=" + mRampDownDuration + + ", mHardwareHapticFeedbackIntensity=" + + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK)) + ", mHapticFeedbackIntensity=" - + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_TOUCH)) + + intensityToString(getCurrentIntensity(USAGE_TOUCH)) + ", mHapticFeedbackDefaultIntensity=" - + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)) + + intensityToString(getDefaultIntensity(USAGE_TOUCH)) + ", mNotificationIntensity=" - + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION)) + + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION)) + ", mNotificationDefaultIntensity=" - + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)) + + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION)) + ", mRingIntensity=" - + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)) + + intensityToString(getCurrentIntensity(USAGE_RINGTONE)) + ", mRingDefaultIntensity=" - + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)) + + intensityToString(getDefaultIntensity(USAGE_RINGTONE)) + '}'; } @@ -410,15 +432,15 @@ final class VibrationSettings { proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY, mHapticFeedbackIntensity); proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); + getDefaultIntensity(USAGE_TOUCH)); proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY, mNotificationIntensity); proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); + getDefaultIntensity(USAGE_NOTIFICATION)); proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY, mRingIntensity); proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + getDefaultIntensity(USAGE_RINGTONE)); } } diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index a3273823c42b..fdd991358154 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -287,6 +287,9 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } // If we waited, the queue may have changed, so let the loop run again. if (waitTime <= 0) { + if (DEBUG) { + Slog.d(TAG, "Play vibration consuming next step..."); + } mStepQueue.consumeNext(); } Vibration.Status status = mStop ? Vibration.Status.CANCELLED diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index 4b7fd904e32d..4a1b95bd4596 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -40,21 +40,22 @@ final class VibratorController { private static final int SUGGESTED_FREQUENCY_SAFE_RANGE = 200; private final Object mLock = new Object(); - private final NativeWrapper mNativeWrapper; @GuardedBy("mLock") - private VibratorInfo mVibratorInfo; - @GuardedBy("mLock") - private boolean mVibratorInfoLoadSuccessful; - @GuardedBy("mLock") + private final NativeWrapper mNativeWrapper; + + // Vibrator state listeners that support concurrent updates and broadcasts, but should lock + // while broadcasting to guarantee delivery order. private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners = new RemoteCallbackList<>(); - @GuardedBy("mLock") - private boolean mIsVibrating; - @GuardedBy("mLock") - private boolean mIsUnderExternalControl; - @GuardedBy("mLock") - private float mCurrentAmplitude; + + // Vibrator state variables that are updated from synchronized blocks but can be read anytime + // for a snippet of the current known vibrator state/info. + private volatile VibratorInfo mVibratorInfo; + private volatile boolean mVibratorInfoLoadSuccessful; + private volatile boolean mIsVibrating; + private volatile boolean mIsUnderExternalControl; + private volatile float mCurrentAmplitude; /** Listener for vibration completion callbacks from native. */ public interface OnVibrationCompleteListener { @@ -86,35 +87,39 @@ final class VibratorController { /** Register state listener for this vibrator. */ public boolean registerVibratorStateListener(IVibratorStateListener listener) { - synchronized (mLock) { - final long token = Binder.clearCallingIdentity(); - try { + final long token = Binder.clearCallingIdentity(); + try { + // Register the listener and send the first state atomically, to avoid potentially + // out of order broadcasts in between. + synchronized (mLock) { if (!mVibratorStateListeners.register(listener)) { return false; } // Notify its callback after new client registered. - notifyStateListenerLocked(listener); - return true; - } finally { - Binder.restoreCallingIdentity(token); + notifyStateListener(listener, mIsVibrating); } + return true; + } finally { + Binder.restoreCallingIdentity(token); } } /** Remove registered state listener for this vibrator. */ public boolean unregisterVibratorStateListener(IVibratorStateListener listener) { - synchronized (mLock) { - final long token = Binder.clearCallingIdentity(); - try { - return mVibratorStateListeners.unregister(listener); - } finally { - Binder.restoreCallingIdentity(token); - } + final long token = Binder.clearCallingIdentity(); + try { + return mVibratorStateListeners.unregister(listener); + } finally { + Binder.restoreCallingIdentity(token); } } /** Reruns the query to the vibrator to load the {@link VibratorInfo}, if not yet successful. */ public void reloadVibratorInfoIfNeeded() { + // Early check outside lock, for quick return. + if (mVibratorInfoLoadSuccessful) { + return; + } synchronized (mLock) { if (mVibratorInfoLoadSuccessful) { return; @@ -132,16 +137,12 @@ final class VibratorController { /** Checks if the {@link VibratorInfo} was loaded from the vibrator hardware successfully. */ boolean isVibratorInfoLoadSuccessful() { - synchronized (mLock) { - return mVibratorInfoLoadSuccessful; - } + return mVibratorInfoLoadSuccessful; } /** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */ public VibratorInfo getVibratorInfo() { - synchronized (mLock) { - return mVibratorInfo; - } + return mVibratorInfo; } /** @@ -151,9 +152,7 @@ final class VibratorController { * automatically notified to any registered {@link IVibratorStateListener} on change. */ public boolean isVibrating() { - synchronized (mLock) { - return mIsVibrating; - } + return mIsVibrating; } /** @@ -168,16 +167,12 @@ final class VibratorController { * <p>If {@link #isVibrating()} is false then this will be zero. */ public float getCurrentAmplitude() { - synchronized (mLock) { - return mCurrentAmplitude; - } + return mCurrentAmplitude; } /** Return {@code true} if this vibrator is under external control, false otherwise. */ public boolean isUnderExternalControl() { - synchronized (mLock) { - return mIsUnderExternalControl; - } + return mIsUnderExternalControl; } /** @@ -187,14 +182,14 @@ final class VibratorController { * @return true if this vibrator has this capability, false otherwise */ public boolean hasCapability(long capability) { - synchronized (mLock) { - return mVibratorInfo.hasCapability(capability); - } + return mVibratorInfo.hasCapability(capability); } /** Return {@code true} if the underlying vibrator is currently available, false otherwise. */ public boolean isAvailable() { - return mNativeWrapper.isAvailable(); + synchronized (mLock) { + return mNativeWrapper.isAvailable(); + } } /** @@ -203,10 +198,10 @@ final class VibratorController { * <p>This will affect the state of {@link #isUnderExternalControl()}. */ public void setExternalControl(boolean externalControl) { + if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { + return; + } synchronized (mLock) { - if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { - return; - } mIsUnderExternalControl = externalControl; mNativeWrapper.setExternalControl(externalControl); } @@ -217,10 +212,10 @@ final class VibratorController { * if given {@code effect} is {@code null}. */ public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) { + if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { + return; + } synchronized (mLock) { - if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { - return; - } if (prebaked == null) { mNativeWrapper.alwaysOnDisable(id); } else { @@ -256,7 +251,7 @@ final class VibratorController { long duration = mNativeWrapper.on(milliseconds, vibrationId); if (duration > 0) { mCurrentAmplitude = -1; - notifyVibratorOnLocked(); + notifyListenerOnVibrating(true); } return duration; } @@ -277,7 +272,7 @@ final class VibratorController { prebaked.getEffectStrength(), vibrationId); if (duration > 0) { mCurrentAmplitude = -1; - notifyVibratorOnLocked(); + notifyListenerOnVibrating(true); } return duration; } @@ -293,14 +288,14 @@ final class VibratorController { * do not support the input or a negative number if the operation failed. */ public long on(PrimitiveSegment[] primitives, long vibrationId) { + if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { + return 0; + } synchronized (mLock) { - if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { - return 0; - } long duration = mNativeWrapper.compose(primitives, vibrationId); if (duration > 0) { mCurrentAmplitude = -1; - notifyVibratorOnLocked(); + notifyListenerOnVibrating(true); } return duration; } @@ -315,15 +310,15 @@ final class VibratorController { * @return The duration of the effect playing, or 0 if unsupported. */ public long on(RampSegment[] primitives, long vibrationId) { + if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { + return 0; + } synchronized (mLock) { - if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { - return 0; - } int braking = mVibratorInfo.getDefaultBraking(); long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId); if (duration > 0) { mCurrentAmplitude = -1; - notifyVibratorOnLocked(); + notifyListenerOnVibrating(true); } return duration; } @@ -334,7 +329,7 @@ final class VibratorController { synchronized (mLock) { mNativeWrapper.off(); mCurrentAmplitude = 0; - notifyVibratorOffLocked(); + notifyListenerOnVibrating(false); } } @@ -349,51 +344,31 @@ final class VibratorController { @Override public String toString() { - synchronized (mLock) { - return "VibratorController{" - + "mVibratorInfo=" + mVibratorInfo - + ", mVibratorInfoLoadSuccessful=" + mVibratorInfoLoadSuccessful - + ", mIsVibrating=" + mIsVibrating - + ", mCurrentAmplitude=" + mCurrentAmplitude - + ", mIsUnderExternalControl=" + mIsUnderExternalControl - + ", mVibratorStateListeners count=" - + mVibratorStateListeners.getRegisteredCallbackCount() - + '}'; - } + return "VibratorController{" + + "mVibratorInfo=" + mVibratorInfo + + ", mVibratorInfoLoadSuccessful=" + mVibratorInfoLoadSuccessful + + ", mIsVibrating=" + mIsVibrating + + ", mCurrentAmplitude=" + mCurrentAmplitude + + ", mIsUnderExternalControl=" + mIsUnderExternalControl + + ", mVibratorStateListeners count=" + + mVibratorStateListeners.getRegisteredCallbackCount() + + '}'; } @GuardedBy("mLock") - private void notifyVibratorOnLocked() { - if (!mIsVibrating) { - mIsVibrating = true; - notifyStateListenersLocked(); + private void notifyListenerOnVibrating(boolean isVibrating) { + if (mIsVibrating != isVibrating) { + mIsVibrating = isVibrating; + // The broadcast method is safe w.r.t. register/unregister listener methods, but lock + // is required here to guarantee delivery order. + mVibratorStateListeners.broadcast( + listener -> notifyStateListener(listener, isVibrating)); } } - @GuardedBy("mLock") - private void notifyVibratorOffLocked() { - if (mIsVibrating) { - mIsVibrating = false; - notifyStateListenersLocked(); - } - } - - @GuardedBy("mLock") - private void notifyStateListenersLocked() { - final int length = mVibratorStateListeners.beginBroadcast(); - try { - for (int i = 0; i < length; i++) { - notifyStateListenerLocked(mVibratorStateListeners.getBroadcastItem(i)); - } - } finally { - mVibratorStateListeners.finishBroadcast(); - } - } - - @GuardedBy("mLock") - private void notifyStateListenerLocked(IVibratorStateListener listener) { + private void notifyStateListener(IVibratorStateListener listener, boolean isVibrating) { try { - listener.onVibrating(mIsVibrating); + listener.onVibrating(isVibrating); } catch (RemoteException | RuntimeException e) { Slog.e(TAG, "Vibrator state listener failed to call", e); } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 0675ad6451ce..5d40c23610dd 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -391,6 +391,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { fillVibrationFallbacks(vib, effect); synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Starting vibrate for vibration " + vib.id); + } Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib); if (ignoreStatus != null) { endVibrationLocked(vib, ignoreStatus); @@ -498,6 +501,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @VisibleForTesting void updateServiceState() { synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Updating device state..."); + } boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators( mVibrationSettings.shouldVibrateInputDevices()); @@ -611,6 +617,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); try { Vibration vib = mCurrentVibration.getVibration(); + if (DEBUG) { + Slog.d(TAG, "Reporting vibration " + vib.id + " finished with status " + status); + } endVibrationLocked(vib, status); finishAppOpModeLocked(vib.uid, vib.opPkg); } finally { @@ -1062,11 +1071,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.d(TAG, "Vibrators released after finished vibration"); } synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Processing vibrators released callback"); + } mCurrentVibration = null; if (mNextVibration != null) { VibrationThread vibThread = mNextVibration; mNextVibration = null; - startVibrationThreadLocked(vibThread); + Vibration.Status status = startVibrationThreadLocked(vibThread); + if (status != Vibration.Status.RUNNING) { + endVibrationLocked(vibThread.getVibration(), status); + } } } } @@ -1248,6 +1263,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { void dumpText(PrintWriter pw) { pw.println("Vibrator Manager Service:"); synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Dumping vibrator manager service to text..."); + } pw.println(" mVibrationSettings:"); pw.println(" " + mVibrationSettings); pw.println(); @@ -1290,6 +1308,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { final ProtoOutputStream proto = new ProtoOutputStream(fd); synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Dumping vibrator manager service to proto..."); + } mVibrationSettings.dumpProto(proto); if (mCurrentVibration != null) { mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto, diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ea22d92c1e20..2a1bb0b87d8b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6224,15 +6224,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A public boolean inputDispatchingTimedOut(String reason, int windowPid) { ActivityRecord anrActivity; WindowProcessController anrApp; - boolean windowFromSameProcessAsActivity; + boolean blameActivityProcess; synchronized (mAtmService.mGlobalLock) { anrActivity = getWaitingHistoryRecordLocked(); anrApp = app; - windowFromSameProcessAsActivity = - !hasProcess() || app.getPid() == windowPid || windowPid == INVALID_PID; + blameActivityProcess = hasProcess() + && (app.getPid() == windowPid || windowPid == INVALID_PID); } - if (windowFromSameProcessAsActivity) { + if (blameActivityProcess) { return mAtmService.mAmInternal.inputDispatchingTimedOut(anrApp.mOwner, anrActivity.shortComponentName, anrActivity.info.applicationInfo, shortComponentName, app, false, reason); diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 210b0aebceb2..ecc85878353a 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -496,11 +496,13 @@ public class ActivityStartController { * @param activityIntent intent to start the activity. * @param activityOptions ActivityOptions to start the activity with. * @param resultTo the caller activity + * @param callingUid the caller uid + * @param callingPid the caller pid * @return the start result. */ int startActivityInTaskFragment(@NonNull TaskFragment taskFragment, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, - @Nullable IBinder resultTo) { + @Nullable IBinder resultTo, int callingUid, int callingPid) { final ActivityRecord caller = resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null; return obtainStarter(activityIntent, "startActivityInTaskFragment") @@ -508,8 +510,8 @@ public class ActivityStartController { .setInTaskFragment(taskFragment) .setResultTo(resultTo) .setRequestCode(-1) - .setCallingUid(Binder.getCallingUid()) - .setCallingPid(Binder.getCallingPid()) + .setCallingUid(callingUid) + .setCallingPid(callingPid) .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId()) .execute(); } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index df9a6d21113e..878822522d08 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -590,7 +590,7 @@ public class AppTransitionController { } // We don't want the organizer to handle transition of non-embedded activity of other // app. - if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) { + if (r.getUid() != task.effectiveUid && !r.isEmbedded()) { leafTask = null; break; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 42c81248da34..55acfb668859 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -218,6 +218,7 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManagerPolicyConstants.PointerEventListener; +import android.window.DisplayWindowPolicyController; import android.window.IDisplayAreaOrganizer; import com.android.internal.annotations.VisibleForTesting; @@ -696,6 +697,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // well and thus won't change the top resumed / focused record boolean mDontMoveToTop; + /** + * The policy controller of the windows that can be displayed on the virtual display. + * + * @see DisplayWindowPolicyController + */ + @Nullable + DisplayWindowPolicyController mDisplayWindowPolicyController; + private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; final ActivityRecord activity = w.mActivityRecord; @@ -2739,6 +2748,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (newDisplayInfo != null) { mDisplayInfo.copyFrom(newDisplayInfo); } + + mDisplayWindowPolicyController = + displayManagerInternal.getDisplayWindowPolicyController(mDisplayId); } updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, @@ -3420,6 +3432,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInputMonitor.dump(pw, " "); pw.println(); mInsetsStateController.dump(prefix, pw); + if (mDisplayWindowPolicyController != null) { + pw.println(); + mDisplayWindowPolicyController.dump(prefix, pw); + } } @Override diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index badb1f5a0a12..963f3265757d 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -139,9 +139,6 @@ class EnsureActivitiesVisibleHelper { setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity); } } - if (mTaskFragment.mTransitionController.isShellTransitionsEnabled()) { - mTaskFragment.getDisplayContent().mWallpaperController.adjustWallpaperWindows(); - } } private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting, diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java index 3de98f18dae8..7a7fb6570694 100644 --- a/services/core/java/com/android/server/wm/PackageConfigPersister.java +++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java @@ -169,21 +169,35 @@ public class PackageConfigPersister { } } + /** + * Returns true when the app specific configuration is successfully stored or removed based on + * the current requested configuration. It will return false when the requested + * configuration is same as the pre-existing app-specific configuration. + */ @GuardedBy("mLock") boolean updateFromImpl(String packageName, int userId, PackageConfigurationUpdaterImpl impl) { synchronized (mLock) { - PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId); - if (impl.getNightMode() != null) { - record.mNightMode = impl.getNightMode(); - } - if (impl.getLocales() != null) { - record.mLocales = impl.getLocales(); + boolean isRecordPresent = false; + PackageConfigRecord record = findRecord(mModified, packageName, userId); + if (record != null) { + isRecordPresent = true; + } else { + record = findRecordOrCreate(mModified, packageName, userId); } + boolean isNightModeChanged = updateNightMode(impl.getNightMode(), record); + boolean isLocalesChanged = updateLocales(impl.getLocales(), record); + if ((record.mNightMode == null || record.isResetNightMode()) && (record.mLocales == null || record.mLocales.isEmpty())) { // if all values default to system settings, we can remove the package. removePackage(packageName, userId); + // if there was a pre-existing record for the package that was deleted, + // we return true (since it was successfully deleted), else false (since there was + // no change to the previous state). + return isRecordPresent; + } else if (!isNightModeChanged && !isLocalesChanged) { + return false; } else { final PackageConfigRecord pendingRecord = findRecord(mPendingWrite, record.mName, record.mUserId); @@ -195,7 +209,8 @@ public class PackageConfigPersister { writeRecord = pendingRecord; } - if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) { + if (!updateNightMode(record.mNightMode, writeRecord) + && !updateLocales(record.mLocales, writeRecord)) { return false; } @@ -203,24 +218,24 @@ public class PackageConfigPersister { Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord); } mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */); + return true; } - return true; } } - private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) { - if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) { + private boolean updateNightMode(Integer requestedNightMode, PackageConfigRecord record) { + if (requestedNightMode == null || requestedNightMode.equals(record.mNightMode)) { return false; } - writeRecord.mNightMode = record.mNightMode; + record.mNightMode = requestedNightMode; return true; } - private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) { - if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) { + private boolean updateLocales(LocaleList requestedLocaleList, PackageConfigRecord record) { + if (requestedLocaleList == null || requestedLocaleList.equals(record.mLocales)) { return false; } - writeRecord.mLocales = record.mLocales; + record.mLocales = requestedLocaleList; return true; } diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 6d83fb6d12c9..29c27f9f3af6 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -31,10 +31,8 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.util.Slog; import android.view.RemoteAnimationDefinition; -import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; import com.android.internal.protolog.common.ProtoLog; @@ -135,11 +133,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName()); final TaskFragmentInfo info = tf.getTaskFragmentInfo(); - final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(), - "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared"); try { - organizer.onTaskFragmentAppeared( - new TaskFragmentAppearedInfo(info, outSurfaceControl)); + organizer.onTaskFragmentAppeared(info); mLastSentTaskFragmentInfos.put(tf, info); tf.mTaskFragmentAppearedSent = true; } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f175eec15064..7349594483a8 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -244,11 +244,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } mParticipants.add(wc); if (info.mShowWallpaper) { - // Collect the wallpaper so it is part of the sync set. - final WindowContainer wallpaper = + // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set. + final WindowState wallpaper = wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper(); if (wallpaper != null) { - collect(wallpaper); + collect(wallpaper.mToken); } } } @@ -495,25 +495,35 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); return; } - int displayId = DEFAULT_DISPLAY; - for (WindowContainer container : mParticipants) { - if (container.mDisplayContent == null) continue; - displayId = container.mDisplayContent.getDisplayId(); + boolean hasWallpaper = false; + DisplayContent dc = null; + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final WindowContainer<?> wc = mParticipants.valueAt(i); + if (dc == null && wc.mDisplayContent != null) { + dc = wc.mDisplayContent; + } + if (!hasWallpaper && isWallpaper(wc)) { + hasWallpaper = true; + } } + if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay(); if (mState == STATE_ABORT) { mController.abort(this); - mController.mAtm.mRootWindowContainer.getDisplayContent(displayId) - .getPendingTransaction().merge(transaction); + dc.getPendingTransaction().merge(transaction); mSyncId = -1; mOverrideOptions = null; return; } + // Ensure that wallpaper visibility is updated with the latest wallpaper target. + if (hasWallpaper) { + dc.mWallpaperController.adjustWallpaperWindows(); + } mState = STATE_PLAYING; mController.moveToPlaying(this); - if (mController.mAtm.mTaskSupervisor.getKeyguardController().isKeyguardLocked(displayId)) { + if (dc.isKeyguardLocked()) { mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; } @@ -523,9 +533,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe info.setAnimationOptions(mOverrideOptions); // TODO(b/188669821): Move to animation impl in shell. - handleLegacyRecentsStartBehavior(displayId, info); + handleLegacyRecentsStartBehavior(dc, info); - handleNonAppWindowsInTransition(displayId, mType, mFlags); + handleNonAppWindowsInTransition(dc, mType, mFlags); reportStartReasonsToLogger(); @@ -627,14 +637,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } /** @see RecentsAnimationController#attachNavigationBarToApp */ - private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) { + private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) { if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) { return; } - final DisplayContent dc = - mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); - if (dc == null) return; - mRecentsDisplayId = displayId; + mRecentsDisplayId = dc.mDisplayId; // Recents has an input-consumer to grab input from the "live tile" app. Set that up here final InputConsumerImpl recentsAnimationInputConsumer = @@ -679,7 +686,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Find the top-most non-home, closing app. for (int i = 0; i < info.getChanges().size(); ++i) { final TransitionInfo.Change c = info.getChanges().get(i); - if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId + if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) { continue; @@ -710,7 +717,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe t.setLayer(navSurfaceControl, Integer.MAX_VALUE); } if (mController.mStatusBar != null) { - mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false); + mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false); } } @@ -760,13 +767,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } - private void handleNonAppWindowsInTransition(int displayId, + private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc, @TransitionType int transit, @TransitionFlags int flags) { - final DisplayContent dc = - mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); - if (dc == null) { - return; - } if ((transit == TRANSIT_KEYGUARD_GOING_AWAY || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 08b1a2f39953..e24be378d29a 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -611,8 +611,9 @@ class WallpaperController { private void updateWallpaperTokens(boolean visible) { for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); - token.updateWallpaperWindows(visible); - token.getDisplayContent().assignWindowLayers(false); + if (token.updateWallpaperWindows(visible)) { + token.mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */); + } } } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 3a639f50603f..fe405e5b3af8 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -104,18 +104,21 @@ class WallpaperWindowToken extends WindowToken { } } - void updateWallpaperWindows(boolean visible) { + /** Returns {@code true} if visibility is changed. */ + boolean updateWallpaperWindows(boolean visible) { + boolean changed = false; if (isVisible() != visible) { ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b", token, visible); setVisibility(visible); + changed = true; } - final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; if (mTransitionController.isShellTransitionsEnabled()) { - return; + return changed; } - final WindowState wallpaperTarget = wallpaperController.getWallpaperTarget(); + final WindowState wallpaperTarget = + mDisplayContent.mWallpaperController.getWallpaperTarget(); if (visible && wallpaperTarget != null) { final RecentsAnimationController recentsAnimationController = @@ -137,6 +140,7 @@ class WallpaperWindowToken extends WindowToken { } setVisible(visible); + return changed; } private void setVisible(boolean visible) { @@ -155,10 +159,12 @@ class WallpaperWindowToken extends WindowToken { * transition. In that situation, make sure to call {@link #commitVisibility} when done. */ void setVisibility(boolean visible) { - // Before setting mVisibleRequested so we can track changes. - mTransitionController.collect(this); + if (mVisibleRequested != visible) { + // Before setting mVisibleRequested so we can track changes. + mTransitionController.collect(this); - setVisibleRequested(visible); + setVisibleRequested(visible); + } // If in a transition, defer commits for activities that are going invisible if (!visible && (mTransitionController.inTransition() diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 3d479d1e0d68..0649b25e0f04 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -587,7 +587,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: { final TaskFragmentCreationParams taskFragmentCreationOptions = hop.getTaskFragmentCreationOptions(); - createTaskFragment(taskFragmentCreationOptions, errorCallbackToken); + createTaskFragment(taskFragmentCreationOptions, errorCallbackToken, caller); break; } case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: { @@ -630,7 +630,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); final int result = mService.getActivityStartController() .startActivityInTaskFragment(tf, activityIntent, activityOptions, - hop.getCallingActivity()); + hop.getCallingActivity(), caller.mUid, caller.mPid); if (!isStartResultSuccessful(result)) { sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), errorCallbackToken, @@ -1199,7 +1199,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, - @Nullable IBinder errorCallbackToken) { + @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) { final ActivityRecord ownerActivity = ActivityRecord.forTokenLocked(creationParams.getOwnerToken()); final ITaskFragmentOrganizer organizer = ITaskFragmentOrganizer.Stub.asInterface( @@ -1217,9 +1217,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } - // The ownerActivity has to belong to the same app as the root Activity of the target Task. - final ActivityRecord rootActivity = ownerActivity.getTask().getRootActivity(); - if (rootActivity.getUid() != ownerActivity.getUid()) { + // The ownerActivity has to belong to the same app as the target Task. + if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid() + || ownerActivity.getTask().effectiveUid != caller.mUid) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with the ownerToken while " + "the root activity of the target task belong to the different app"); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 0a55003e77fc..e4c8871e6f8d 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -29,6 +29,8 @@ #include <android/hardware/gnss/2.1/IGnssMeasurement.h> #include <android/hardware/gnss/BnGnss.h> #include <android/hardware/gnss/BnGnssCallback.h> +#include <android/hardware/gnss/BnGnssGeofence.h> +#include <android/hardware/gnss/BnGnssGeofenceCallback.h> #include <android/hardware/gnss/BnGnssMeasurementCallback.h> #include <android/hardware/gnss/BnGnssPowerIndicationCallback.h> #include <android/hardware/gnss/BnGnssPsdsCallback.h> @@ -191,6 +193,9 @@ using android::hardware::gnss::IGnssPowerIndicationCallback; using android::hardware::gnss::PsdsType; using IGnssAidl = android::hardware::gnss::IGnss; using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback; +using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching; +using IGnssGeofenceAidl = android::hardware::gnss::IGnssGeofence; +using IGnssGeofenceCallbackAidl = android::hardware::gnss::IGnssGeofenceCallback; using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds; using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback; using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration; @@ -216,6 +221,8 @@ sp<IGnss_V1_1> gnssHal_V1_1 = nullptr; sp<IGnss_V2_0> gnssHal_V2_0 = nullptr; sp<IGnss_V2_1> gnssHal_V2_1 = nullptr; sp<IGnssAidl> gnssHalAidl = nullptr; +sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr; +sp<IGnssGeofenceAidl> gnssGeofenceAidlIface = nullptr; sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr; sp<IGnssXtra> gnssXtraIface = nullptr; sp<IAGnssRil_V1_0> agnssRilIface = nullptr; @@ -709,35 +716,25 @@ Return<void> GnssXtraCallback::downloadRequestCb() { return Void(); } -/* - * GnssGeofenceCallback class implements the callback methods for the - * IGnssGeofence interface. - */ -struct GnssGeofenceCallback : public IGnssGeofenceCallback { - // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow. - Return<void> gnssGeofenceTransitionCb( - int32_t geofenceId, - const GnssLocation_V1_0& location, - GeofenceTransition transition, - hardware::gnss::V1_0::GnssUtcTime timestamp) override; - Return<void> - gnssGeofenceStatusCb( - GeofenceAvailability status, - const GnssLocation_V1_0& location) override; - Return<void> gnssGeofenceAddCb(int32_t geofenceId, - GeofenceStatus status) override; - Return<void> gnssGeofenceRemoveCb(int32_t geofenceId, - GeofenceStatus status) override; - Return<void> gnssGeofencePauseCb(int32_t geofenceId, - GeofenceStatus status) override; - Return<void> gnssGeofenceResumeCb(int32_t geofenceId, - GeofenceStatus status) override; +/** Util class for GnssGeofenceCallback methods. */ +struct GnssGeofenceCallbackUtil { + template <class T> + static void gnssGeofenceTransitionCb(int geofenceId, const T& location, int transition, + int64_t timestampMillis); + template <class T> + static void gnssGeofenceStatusCb(int availability, const T& lastLocation); + static void gnssGeofenceAddCb(int geofenceId, int status); + static void gnssGeofenceRemoveCb(int geofenceId, int status); + static void gnssGeofencePauseCb(int geofenceId, int status); + static void gnssGeofenceResumeCb(int geofenceId, int status); + +private: + GnssGeofenceCallbackUtil() = delete; }; -Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb( - int32_t geofenceId, const GnssLocation_V1_0& location, - GeofenceTransition transition, - hardware::gnss::V1_0::GnssUtcTime timestamp) { +template <class T> +void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location, + int transition, int64_t timestamp) { JNIEnv* env = getJniEnv(); jobject jLocation = translateGnssLocation(env, location); @@ -751,27 +748,22 @@ Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb( checkAndClearExceptionFromCallback(env, __FUNCTION__); env->DeleteLocalRef(jLocation); - return Void(); } -Return<void> -GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability status, - const GnssLocation_V1_0& location) { +template <class T> +void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) { JNIEnv* env = getJniEnv(); - jobject jLocation = translateGnssLocation(env, location); + jobject jLocation = translateGnssLocation(env, lastLocation); - env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, status, - jLocation); + env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation); checkAndClearExceptionFromCallback(env, __FUNCTION__); env->DeleteLocalRef(jLocation); - return Void(); } -Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, - GeofenceStatus status) { +void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) { JNIEnv* env = getJniEnv(); - if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) { + if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) { ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status); } @@ -780,13 +772,11 @@ Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, geofenceId, status); checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); } -Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, - GeofenceStatus status) { +void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) { JNIEnv* env = getJniEnv(); - if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) { + if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) { ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status); } @@ -794,13 +784,11 @@ Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, method_reportGeofenceRemoveStatus, geofenceId, status); checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); } -Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, - GeofenceStatus status) { +void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) { JNIEnv* env = getJniEnv(); - if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) { + if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) { ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status); } @@ -808,13 +796,11 @@ Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, method_reportGeofencePauseStatus, geofenceId, status); checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); } -Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, - GeofenceStatus status) { +void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) { JNIEnv* env = getJniEnv(); - if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) { + if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) { ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status); } @@ -822,6 +808,104 @@ Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, method_reportGeofenceResumeStatus, geofenceId, status); checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +/* + * GnssGeofenceCallbackAidl class implements the callback methods for the IGnssGeofence AIDL + * interface. + */ +struct GnssGeofenceCallbackAidl : public android::hardware::gnss::BnGnssGeofenceCallback { + Status gnssGeofenceTransitionCb(int geofenceId, const GnssLocationAidl& location, + int transition, int64_t timestampMillis) override; + Status gnssGeofenceStatusCb(int availability, const GnssLocationAidl& lastLocation) override; + Status gnssGeofenceAddCb(int geofenceId, int status) override; + Status gnssGeofenceRemoveCb(int geofenceId, int status) override; + Status gnssGeofencePauseCb(int geofenceId, int status) override; + Status gnssGeofenceResumeCb(int geofenceId, int status) override; +}; + +Status GnssGeofenceCallbackAidl::gnssGeofenceTransitionCb(int geofenceId, + const GnssLocationAidl& location, + int transition, int64_t timestampMillis) { + GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, transition, + timestampMillis); + return Status::ok(); +} + +Status GnssGeofenceCallbackAidl::gnssGeofenceStatusCb(int availability, + const GnssLocationAidl& lastLocation) { + GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(availability, lastLocation); + return Status::ok(); +} + +Status GnssGeofenceCallbackAidl::gnssGeofenceAddCb(int geofenceId, int status) { + GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, status); + return Status::ok(); +} + +Status GnssGeofenceCallbackAidl::gnssGeofenceRemoveCb(int geofenceId, int status) { + GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, status); + return Status::ok(); +} + +Status GnssGeofenceCallbackAidl::gnssGeofencePauseCb(int geofenceId, int status) { + GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, status); + return Status::ok(); +} + +Status GnssGeofenceCallbackAidl::gnssGeofenceResumeCb(int geofenceId, int status) { + GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, status); + return Status::ok(); +} + +/* + * GnssGeofenceCallback class implements the callback methods for the + * IGnssGeofence HIDL interface. + */ +struct GnssGeofenceCallback : public IGnssGeofenceCallback { + // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow. + Return<void> gnssGeofenceTransitionCb(int32_t geofenceId, const GnssLocation_V1_0& location, + GeofenceTransition transition, + hardware::gnss::V1_0::GnssUtcTime timestamp) override; + Return<void> gnssGeofenceStatusCb(GeofenceAvailability status, + const GnssLocation_V1_0& location) override; + Return<void> gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) override; + Return<void> gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) override; + Return<void> gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) override; + Return<void> gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) override; +}; + +Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb( + int32_t geofenceId, const GnssLocation_V1_0& location, GeofenceTransition transition, + hardware::gnss::V1_0::GnssUtcTime timestamp) { + GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, (int)transition, + (int64_t)timestamp); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability availability, + const GnssLocation_V1_0& location) { + GnssGeofenceCallbackUtil::gnssGeofenceStatusCb((int)availability, location); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) { + GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, (int)status); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) { + GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, (int)status); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) { + GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, (int)status); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) { + GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, (int)status); return Void(); } @@ -1279,11 +1363,13 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject android_location_gnss_hal_GnssNative_set_gps_service_handle(); } - if (gnssHal == nullptr) { + if (gnssHal == nullptr && gnssHalAidl == nullptr) { ALOGE("Unable to get GPS service\n"); return; } + // TODO: linkToDeath for AIDL HAL + gnssHalDeathRecipient = new GnssDeathRecipient(); hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0); if (!linked.isOk()) { @@ -1303,7 +1389,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } else { ALOGD("Unable to get a handle to PSDS AIDL interface."); } - } else { + } else if (gnssHal != nullptr) { auto gnssXtra = gnssHal->getExtensionXtra(); if (!gnssXtra.isOk()) { ALOGD("Unable to get a handle to Xtra"); @@ -1320,7 +1406,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject agnssRilIface_V2_0 = agnssRil_V2_0; agnssRilIface = agnssRilIface_V2_0; } - } else { + } else if (gnssHal != nullptr) { auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil(); if (!agnssRil_V1_0.isOk()) { ALOGD("Unable to get a handle to AGnssRil"); @@ -1336,7 +1422,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } else { agnssIface_V2_0 = agnss_V2_0; } - } else { + } else if (gnssHal != nullptr) { auto agnss_V1_0 = gnssHal->getExtensionAGnss(); if (!agnss_V1_0.isOk()) { ALOGD("Unable to get a handle to AGnss"); @@ -1345,11 +1431,13 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage(); - if (!gnssNavigationMessage.isOk()) { - ALOGD("Unable to get a handle to GnssNavigationMessage"); - } else { - gnssNavigationMessageIface = gnssNavigationMessage; + if (gnssHal != nullptr) { + auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage(); + if (!gnssNavigationMessage.isOk()) { + ALOGD("Unable to get a handle to GnssNavigationMessage"); + } else { + gnssNavigationMessageIface = gnssNavigationMessage; + } } // Allow all causal combinations between IGnss.hal and IGnssMeasurement.hal. That means, @@ -1387,12 +1475,12 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject std::make_unique<android::gnss::GnssMeasurement_V1_1>(gnssMeasurement); } } - if (gnssMeasurementIface == nullptr) { - auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement(); - if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) { - gnssMeasurementIface = - std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement); - } + if (gnssHal != nullptr && gnssMeasurementIface == nullptr) { + auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement(); + if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) { + gnssMeasurementIface = + std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement); + } } if (gnssHal_V2_1 != nullptr) { @@ -1434,7 +1522,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject gnssDebugIface = gnssDebugIface_V2_0; } } - if (gnssDebugIface == nullptr) { + if (gnssHal != nullptr && gnssDebugIface == nullptr) { auto gnssDebug = gnssHal->getExtensionGnssDebug(); if (!gnssDebug.isOk()) { ALOGD("Unable to get a handle to GnssDebug"); @@ -1443,11 +1531,13 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - auto gnssNi = gnssHal->getExtensionGnssNi(); - if (!gnssNi.isOk()) { - ALOGD("Unable to get a handle to GnssNi"); - } else { - gnssNiIface = gnssNi; + if (gnssHal != nullptr) { + auto gnssNi = gnssHal->getExtensionGnssNi(); + if (!gnssNi.isOk()) { + ALOGD("Unable to get a handle to GnssNi"); + } else { + gnssNiIface = gnssNi; + } } if (gnssHalAidl != nullptr) { @@ -1488,11 +1578,17 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing(); - if (!gnssGeofencing.isOk()) { - ALOGD("Unable to get a handle to GnssGeofencing"); - } else { - gnssGeofencingIface = gnssGeofencing; + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + sp<IGnssGeofenceAidl> gnssGeofenceAidl; + auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofenceAidl); + if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence interface.")) { + gnssGeofenceAidlIface = gnssGeofenceAidl; + } + } else if (gnssHal != nullptr) { + auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing(); + if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) { + gnssGeofencingIface = gnssGeofencing; + } } if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { @@ -1507,7 +1603,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject gnssBatchingIface = std::make_unique<gnss::GnssBatching_V2_0>(gnssBatching_V2_0); } } - if (gnssBatchingIface == nullptr) { + if (gnssHal != nullptr && gnssBatchingIface == nullptr) { auto gnssBatching_V1_0 = gnssHal->getExtensionGnssBatching(); if (checkHidlReturn(gnssBatching_V1_0, "Unable to get a handle to GnssBatching")) { gnssBatchingIface = std::make_unique<gnss::GnssBatching_V1_0>(gnssBatching_V1_0); @@ -1568,7 +1664,7 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl /* * Fail if the main interface fails to initialize */ - if (gnssHal == nullptr) { + if (gnssHal == nullptr && gnssHalAidl == nullptr) { ALOGE("Unable to initialize GNSS HAL."); return JNI_FALSE; } @@ -1583,7 +1679,7 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl result = gnssHal_V2_0->setCallback_2_0(gnssCbIface); } else if (gnssHal_V1_1 != nullptr) { result = gnssHal_V1_1->setCallback_1_1(gnssCbIface); - } else { + } else if (gnssHal != nullptr) { result = gnssHal->setCallback(gnssCbIface); } @@ -1630,10 +1726,18 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl } // Set IGnssGeofencing.hal callback. - sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback(); - if (gnssGeofencingIface != nullptr) { + if (gnssGeofenceAidlIface != nullptr) { + sp<IGnssGeofenceCallbackAidl> gnssGeofenceCallbackAidl = new GnssGeofenceCallbackAidl(); + auto status = gnssGeofenceAidlIface->setCallback(gnssGeofenceCallbackAidl); + if (!checkAidlStatus(status, "IGnssGeofenceAidl setCallback() failed.")) { + gnssGeofenceAidlIface = nullptr; + } + } else if (gnssGeofencingIface != nullptr) { + sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback(); auto status = gnssGeofencingIface->setCallback(gnssGeofencingCbIface); - checkHidlReturn(status, "IGnssGeofencing setCallback() failed."); + if (!checkHidlReturn(status, "IGnssGeofencing setCallback() failed.")) { + gnssGeofencingIface = nullptr; + } } else { ALOGI("Unable to initialize IGnssGeofencing interface."); } @@ -1693,12 +1797,15 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl } static void android_location_gnss_hal_GnssNative_cleanup(JNIEnv* /* env */, jclass) { - if (gnssHal == nullptr) { - return; + if (gnssHalAidl != nullptr) { + auto status = gnssHalAidl->close(); + checkAidlStatus(status, "IGnssAidl close() failed."); } - auto result = gnssHal->cleanup(); - checkHidlReturn(result, "IGnss cleanup() failed."); + if (gnssHal != nullptr) { + auto result = gnssHal->cleanup(); + checkHidlReturn(result, "IGnss cleanup() failed."); + } } static jboolean android_location_gnss_hal_GnssNative_set_position_mode( @@ -2177,57 +2284,85 @@ static void android_location_GnssNetworkConnectivityHandler_update_network_state static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */, jclass) { - return (gnssGeofencingIface != nullptr) ? JNI_TRUE : JNI_FALSE; + if (gnssGeofencingIface == nullptr && gnssGeofenceAidlIface == nullptr) { + return JNI_FALSE; + } + return JNI_TRUE; } static jboolean android_location_gnss_hal_GnssNative_add_geofence( JNIEnv* /* env */, jclass, jint geofenceId, jdouble latitude, jdouble longitude, jdouble radius, jint last_transition, jint monitor_transition, jint notification_responsiveness, jint unknown_timer) { - if (gnssGeofencingIface == nullptr) { - ALOGE("%s: IGnssGeofencing interface not available.", __func__); - return JNI_FALSE; + if (gnssGeofenceAidlIface != nullptr) { + auto status = + gnssGeofenceAidlIface->addGeofence(geofenceId, latitude, longitude, radius, + last_transition, monitor_transition, + notification_responsiveness, unknown_timer); + return checkAidlStatus(status, "IGnssGeofenceAidl addGeofence() failed."); } - auto result = gnssGeofencingIface->addGeofence( - geofenceId, latitude, longitude, radius, - static_cast<IGnssGeofenceCallback::GeofenceTransition>(last_transition), - monitor_transition, notification_responsiveness, unknown_timer); - return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed."); + if (gnssGeofencingIface != nullptr) { + auto result = gnssGeofencingIface + ->addGeofence(geofenceId, latitude, longitude, radius, + static_cast<IGnssGeofenceCallback::GeofenceTransition>( + last_transition), + monitor_transition, notification_responsiveness, + unknown_timer); + return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed."); + } + + ALOGE("%s: IGnssGeofencing interface not available.", __func__); + return JNI_FALSE; } static jboolean android_location_gnss_hal_GnssNative_remove_geofence(JNIEnv* /* env */, jclass, jint geofenceId) { - if (gnssGeofencingIface == nullptr) { - ALOGE("%s: IGnssGeofencing interface not available.", __func__); - return JNI_FALSE; + if (gnssGeofenceAidlIface != nullptr) { + auto status = gnssGeofenceAidlIface->removeGeofence(geofenceId); + return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed."); + } + + if (gnssGeofencingIface != nullptr) { + auto result = gnssGeofencingIface->removeGeofence(geofenceId); + return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed."); } - auto result = gnssGeofencingIface->removeGeofence(geofenceId); - return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed."); + ALOGE("%s: IGnssGeofencing interface not available.", __func__); + return JNI_FALSE; } static jboolean android_location_gnss_hal_GnssNative_pause_geofence(JNIEnv* /* env */, jclass, jint geofenceId) { - if (gnssGeofencingIface == nullptr) { - ALOGE("%s: IGnssGeofencing interface not available.", __func__); - return JNI_FALSE; + if (gnssGeofenceAidlIface != nullptr) { + auto status = gnssGeofenceAidlIface->pauseGeofence(geofenceId); + return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed."); } - auto result = gnssGeofencingIface->pauseGeofence(geofenceId); - return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed."); + if (gnssGeofencingIface != nullptr) { + auto result = gnssGeofencingIface->pauseGeofence(geofenceId); + return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed."); + } + + ALOGE("%s: IGnssGeofencing interface not available.", __func__); + return JNI_FALSE; } static jboolean android_location_gnss_hal_GnssNative_resume_geofence(JNIEnv* /* env */, jclass, jint geofenceId, jint monitor_transition) { - if (gnssGeofencingIface == nullptr) { - ALOGE("%s: IGnssGeofencing interface not available.", __func__); - return JNI_FALSE; + if (gnssGeofenceAidlIface != nullptr) { + auto status = gnssGeofenceAidlIface->resumeGeofence(geofenceId, monitor_transition); + return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed."); } - auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition); - return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed."); + if (gnssGeofencingIface != nullptr) { + auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition); + return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed."); + } + + ALOGE("%s: IGnssGeofencing interface not available.", __func__); + return JNI_FALSE; } static jboolean android_location_gnss_hal_GnssNative_is_antenna_info_supported(JNIEnv* env, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 727f26573fac..3839a9f39158 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -8460,7 +8460,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean hasDeviceOwner() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller)); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) + || canManageUsers(caller) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mOwners.hasDeviceOwner(); } @@ -8640,7 +8642,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return null; } - Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); synchronized (getLockObject()) { if (!mOwners.hasDeviceOwner()) { @@ -8986,7 +8989,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return DevicePolicyManager.STATE_USER_UNMANAGED; } final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(canManageUsers(caller)); + Preconditions.checkCallAuthorization(canManageUsers(caller) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); return getUserProvisioningState(caller.getUserId()); } @@ -9240,7 +9244,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return null; } - Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); return getProfileOwnerNameUnchecked(userHandle); } @@ -16466,7 +16471,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return false; } - Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); long id = mInjector.binderClearCallingIdentity(); try { @@ -16492,7 +16498,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return false; } - Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked()); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9119133add6a..2f9e3344b29c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -373,6 +373,8 @@ public final class SystemServer implements Dumpable { "com.android.server.blob.BlobStoreManagerService"; private static final String APP_SEARCH_MANAGER_SERVICE_CLASS = "com.android.server.appsearch.AppSearchManagerService"; + private static final String ISOLATED_COMPILATION_SERVICE_CLASS = + "com.android.server.compos.IsolatedCompilationService"; private static final String ROLLBACK_MANAGER_SERVICE_CLASS = "com.android.server.rollback.RollbackManagerService"; private static final String ALARM_MANAGER_SERVICE_CLASS = @@ -2718,6 +2720,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(APP_SEARCH_MANAGER_SERVICE_CLASS); t.traceEnd(); + if (SystemProperties.getBoolean("ro.config.isolated_compilation_enabled", false)) { + t.traceBegin("IsolatedCompilationService"); + mSystemServiceManager.startService(ISOLATED_COMPILATION_SERVICE_CLASS); + t.traceEnd(); + } + t.traceBegin("StartMediaCommunicationService"); mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS); t.traceEnd(); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt index bbfa4610feea..cfc81e684a7f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt @@ -29,6 +29,7 @@ import com.android.server.apphibernation.AppHibernationManagerInternal import com.android.server.apphibernation.AppHibernationService import com.android.server.extendedtestutils.wheneverStatic import com.android.server.testutils.whenever +import org.junit.Assert import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -36,7 +37,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -102,9 +102,10 @@ class PackageManagerServiceHibernationTests { val pm = createPackageManagerService() rule.system().validateFinalState() - pm.setPackageStoppedState(TEST_PACKAGE_NAME, true, TEST_USER_ID) TestableLooper.get(this).processAllMessages() - Mockito.clearInvocations(appHibernationManager) + + whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(true) pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) @@ -115,6 +116,31 @@ class PackageManagerServiceHibernationTests { } @Test + fun testExitForceStop_nonExistingAppHibernationManager_doesNotThrowException() { + whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java)) + .thenReturn(null) + + rule.system().stageScanExistingPackage( + TEST_PACKAGE_NAME, + 1L, + rule.system().dataAppDirectory) + val pm = createPackageManagerService() + rule.system().validateFinalState() + + TestableLooper.get(this).processAllMessages() + + whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(true) + + try { + pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) + TestableLooper.get(this).processAllMessages() + } catch (e: Exception) { + Assert.fail("Method throws exception when AppHibernationManager is not ready.\n$e") + } + } + + @Test fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() { rule.system().stageScanExistingPackage( TEST_PACKAGE_NAME, diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index ba580ecaad16..e3c60fdfc697 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -108,6 +108,7 @@ android_test { data: [ ":JobTestApp", + ":StubTestApp", ], java_resources: [ diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 5a0f1ee963a2..4c638d669019 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -28,6 +28,17 @@ <option name="test-file-name" value="SimpleServiceTestApp3.apk" /> </target_preparer> + <!-- Create place to store apks --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="mkdir -p /data/local/tmp/servicestests" /> + <option name="teardown-command" value="rm -rf /data/local/tmp/servicestests"/> + </target_preparer> + + <!-- Load additional APKs onto device --> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/> + </target_preparer> + <option name="test-tag" value="FrameworksServicesTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.frameworks.servicestests" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 086e3c0901a8..a83d51b0e5e7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -68,6 +68,7 @@ import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; +import android.accessibilityservice.MagnificationConfig; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -650,8 +651,12 @@ public class AbstractAccessibilityServiceConnectionTest { final float scale = 1.8f; final float centerX = 50.5f; final float centerY = 100.5f; - when(mMockMagnificationProcessor.setScaleAndCenter(displayId, - scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true); + MagnificationConfig config = new MagnificationConfig.Builder() + .setScale(scale) + .setCenterX(centerX) + .setCenterY(centerY).build(); + when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true, + SERVICE_ID)).thenReturn(true); when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false); final boolean result = mServiceConnection.setMagnificationScaleAndCenter( @@ -665,8 +670,12 @@ public class AbstractAccessibilityServiceConnectionTest { final float scale = 1.8f; final float centerX = 50.5f; final float centerY = 100.5f; - when(mMockMagnificationProcessor.setScaleAndCenter(displayId, - scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true); + MagnificationConfig config = new MagnificationConfig.Builder() + .setScale(scale) + .setCenterX(centerX) + .setCenterY(centerY).build(); + when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true, + SERVICE_ID)).thenReturn(true); when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final boolean result = mServiceConnection.setMagnificationScaleAndCenter( diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java index c412b9403561..2fd28167df16 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java @@ -16,25 +16,32 @@ package com.android.server.accessibility; +import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE; +import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.accessibilityservice.MagnificationConfig; import android.graphics.Region; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationProcessor; +import com.android.server.accessibility.magnification.WindowMagnificationManager; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; - +import org.mockito.stubbing.Answer; /** * Tests for the {@link MagnificationProcessor} @@ -42,57 +49,115 @@ import org.mockito.MockitoAnnotations; public class MagnificationProcessorTest { private static final int TEST_DISPLAY = 0; - + private static final int SERVICE_ID = 42; + private static final float TEST_SCALE = 1.8f; + private static final float TEST_CENTER_X = 50.5f; + private static final float TEST_CENTER_Y = 100.5f; private MagnificationProcessor mMagnificationProcessor; @Mock private MagnificationController mMockMagnificationController; @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; + @Mock + private WindowMagnificationManager mMockWindowMagnificationManager; + FullScreenMagnificationControllerStub mFullScreenMagnificationControllerStub; + WindowMagnificationManagerStub mWindowMagnificationManagerStub; @Before + public void setup() { MockitoAnnotations.initMocks(this); - + mFullScreenMagnificationControllerStub = new FullScreenMagnificationControllerStub( + mMockFullScreenMagnificationController); + mWindowMagnificationManagerStub = new WindowMagnificationManagerStub( + mMockWindowMagnificationManager); when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn( mMockFullScreenMagnificationController); + when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn( + mMockWindowMagnificationManager); mMagnificationProcessor = new MagnificationProcessor(mMockMagnificationController); } @Test - public void getScale() { - final float result = 2; - when(mMockFullScreenMagnificationController.getScale(TEST_DISPLAY)).thenReturn(result); + public void getScale_fullscreenMode_expectedValue() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setScale(TEST_SCALE).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + float scale = mMagnificationProcessor.getScale(TEST_DISPLAY); + + assertEquals(scale, TEST_SCALE, 0); + } + + @Test + public void getScale_windowMode_expectedValue() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setScale(TEST_SCALE).build(); + setMagnificationActivated(TEST_DISPLAY, config); float scale = mMagnificationProcessor.getScale(TEST_DISPLAY); - assertEquals(scale, result, 0); + assertEquals(scale, TEST_SCALE, 0); } @Test - public void getCenterX_canControlMagnification_returnCenterX() { - final float result = 200; - when(mMockFullScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(result); + public void getCenterX_canControlFullscreenMagnification_returnCenterX() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setCenterX(TEST_CENTER_X).build(); + setMagnificationActivated(TEST_DISPLAY, config); float centerX = mMagnificationProcessor.getCenterX( TEST_DISPLAY, /* canControlMagnification= */true); - assertEquals(centerX, result, 0); + assertEquals(centerX, TEST_CENTER_X, 0); } @Test - public void getCenterY_canControlMagnification_returnCenterY() { - final float result = 300; - when(mMockFullScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(result); + public void getCenterX_canControlWindowMagnification_returnCenterX() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setCenterX(TEST_CENTER_X).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + float centerX = mMagnificationProcessor.getCenterX( + TEST_DISPLAY, /* canControlMagnification= */true); + + assertEquals(centerX, TEST_CENTER_X, 0); + } + + @Test + public void getCenterY_canControlFullscreenMagnification_returnCenterY() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); float centerY = mMagnificationProcessor.getCenterY( TEST_DISPLAY, /* canControlMagnification= */false); - assertEquals(centerY, result, 0); + assertEquals(centerY, TEST_CENTER_Y, 0); } @Test - public void getMagnificationRegion_canControlMagnification_returnRegion() { + public void getCenterY_canControlWindowMagnification_returnCenterY() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + float centerY = mMagnificationProcessor.getCenterY( + TEST_DISPLAY, /* canControlMagnification= */false); + + assertEquals(centerY, TEST_CENTER_Y, 0); + } + + @Test + public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() { final Region region = new Region(10, 20, 100, 200); + setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE); mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, region, /* canControlMagnification= */true); @@ -101,14 +166,25 @@ public class MagnificationProcessorTest { } @Test - public void getMagnificationRegion_notRegistered_shouldRegisterThenUnregister() { + public void getMagnificationRegion_canControlWindowMagnification_returnRegion() { final Region region = new Region(10, 20, 100, 200); + setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE); + mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, + region, /* canControlMagnification= */true); + + verify(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY), + eq(region)); + } + + @Test + public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() { + final Region region = new Region(10, 20, 100, 200); + setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE); doAnswer((invocation) -> { ((Region) invocation.getArguments()[1]).set(region); return null; }).when(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY), any()); - when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false); final Region result = new Region(); mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, @@ -119,44 +195,242 @@ public class MagnificationProcessorTest { } @Test - public void getMagnificationCenterX_notRegistered_shouldRegisterThenUnregister() { - final float centerX = 480.0f; - when(mMockFullScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(centerX); - when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false); + public void getMagnificationCenterX_fullscreenModeNotRegistered_shouldRegisterThenUnregister() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setCenterX(TEST_CENTER_X).build(); + setMagnificationActivated(TEST_DISPLAY, config); final float result = mMagnificationProcessor.getCenterX( TEST_DISPLAY, /* canControlMagnification= */ true); - assertEquals(centerX, result, 0); + assertEquals(TEST_CENTER_X, result, 0); verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY); verify(mMockFullScreenMagnificationController).unregister(TEST_DISPLAY); } @Test - public void getMagnificationCenterY_notRegistered_shouldRegisterThenUnregister() { - final float centerY = 640.0f; - when(mMockFullScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(centerY); - when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false); + public void getMagnificationCenterY_fullscreenModeNotRegistered_shouldRegisterThenUnregister() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); final float result = mMagnificationProcessor.getCenterY( TEST_DISPLAY, /* canControlMagnification= */ true); - assertEquals(centerY, result, 0); + assertEquals(TEST_CENTER_Y, result, 0); verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY); verify(mMockFullScreenMagnificationController).unregister(TEST_DISPLAY); } @Test - public void setMagnificationScaleAndCenter_notRegistered_shouldRegister() { - final int serviceId = 42; - final float scale = 1.8f; - final float centerX = 50.5f; - final float centerY = 100.5f; - when(mMockFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, - scale, centerX, centerY, true, serviceId)).thenReturn(true); - when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false); + public void getCurrentMode_configDefaultMode_returnActivatedMode() { + final int targetMode = WINDOW_MODE; + setMagnificationActivated(TEST_DISPLAY, targetMode); + + int currentMode = mMagnificationProcessor.getControllingMode(TEST_DISPLAY); + + assertEquals(WINDOW_MODE, currentMode); + } + + @Test + public void reset_fullscreenMagnificationActivated() { + setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE); + + mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false); + + verify(mMockFullScreenMagnificationController).reset(TEST_DISPLAY, false); + } - final boolean result = mMagnificationProcessor.setScaleAndCenter( - TEST_DISPLAY, scale, centerX, centerY, true, serviceId); + @Test + public void reset_windowMagnificationActivated() { + setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE); + + mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false); + + verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY); + } + + @Test + public void setMagnificationConfig_fullscreenModeNotRegistered_shouldRegister() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + final boolean result = mMagnificationProcessor.setMagnificationConfig( + TEST_DISPLAY, config, true, SERVICE_ID); assertTrue(result); verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY); } + + @Test + public void setMagnificationConfig_windowMode_enableMagnification() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + final boolean result = mMagnificationProcessor.setMagnificationConfig( + TEST_DISPLAY, config, true, SERVICE_ID); + + assertTrue(result); + } + + @Test + public void setMagnificationConfig_fullscreenEnabled_expectedConfigValues() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + // mMockFullScreenMagnificationController.unregister(TEST_DISPLAY); + mMagnificationProcessor.setMagnificationConfig( + TEST_DISPLAY, config, true, SERVICE_ID); + + final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig( + TEST_DISPLAY); + + assertConfigEquals(config, result); + } + + @Test + public void setMagnificationConfig_windowEnabled_expectedConfigValues() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + mMagnificationProcessor.setMagnificationConfig( + TEST_DISPLAY, config, true, SERVICE_ID); + + final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig( + TEST_DISPLAY); + + assertConfigEquals(config, result); + } + + private void setMagnificationActivated(int displayId, int configMode) { + setMagnificationActivated(displayId, + new MagnificationConfig.Builder().setMode(configMode).build()); + } + + private void setMagnificationActivated(int displayId, MagnificationConfig config) { + when(mMockMagnificationController.isActivated(displayId, config.getMode())).thenReturn( + true); + mMagnificationProcessor.setMagnificationConfig(displayId, config, false, SERVICE_ID); + if (config.getMode() == FULLSCREEN_MODE) { + when(mMockMagnificationController.isActivated(displayId, WINDOW_MODE)).thenReturn( + false); + mFullScreenMagnificationControllerStub.resetAndStubMethods(); + mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(), + config.getCenterX(), config.getCenterY(), true, SERVICE_ID); + } else if (config.getMode() == WINDOW_MODE) { + when(mMockMagnificationController.isActivated(displayId, FULLSCREEN_MODE)).thenReturn( + false); + mWindowMagnificationManagerStub.resetAndStubMethods(); + mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(), + config.getCenterX(), config.getCenterY()); + } + } + + private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig actual) { + assertEquals(expected.getMode(), actual.getMode()); + assertEquals(expected.getScale(), actual.getScale(), 0); + assertEquals(expected.getCenterX(), actual.getCenterX(), 0); + assertEquals(expected.getCenterY(), actual.getCenterY(), 0); + } + + private static class FullScreenMagnificationControllerStub { + private final FullScreenMagnificationController mScreenMagnificationController; + private float mScale = 1.0f; + private float mCenterX = 0; + private float mCenterY = 0; + private boolean mIsRegistered = false; + + FullScreenMagnificationControllerStub( + FullScreenMagnificationController screenMagnificationController) { + mScreenMagnificationController = screenMagnificationController; + } + + private void stubMethods() { + doAnswer(invocation -> mScale).when(mScreenMagnificationController).getScale( + TEST_DISPLAY); + doAnswer(invocation -> mCenterX).when(mScreenMagnificationController).getCenterX( + TEST_DISPLAY); + doAnswer(invocation -> mCenterY).when(mScreenMagnificationController).getCenterY( + TEST_DISPLAY); + doAnswer(invocation -> mIsRegistered).when(mScreenMagnificationController).isRegistered( + TEST_DISPLAY); + Answer enableMagnificationStubAnswer = invocation -> { + mScale = invocation.getArgument(1); + mCenterX = invocation.getArgument(2); + mCenterY = invocation.getArgument(3); + return true; + }; + doAnswer(enableMagnificationStubAnswer).when( + mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(), + anyFloat(), anyFloat(), eq(true), eq(SERVICE_ID)); + + Answer registerStubAnswer = invocation -> { + mIsRegistered = true; + return true; + }; + doAnswer(registerStubAnswer).when( + mScreenMagnificationController).register(eq(TEST_DISPLAY)); + + Answer unregisterStubAnswer = invocation -> { + mIsRegistered = false; + return true; + }; + doAnswer(unregisterStubAnswer).when( + mScreenMagnificationController).unregister(eq(TEST_DISPLAY)); + } + + public void resetAndStubMethods() { + Mockito.reset(mScreenMagnificationController); + stubMethods(); + } + } + + private static class WindowMagnificationManagerStub { + private final WindowMagnificationManager mWindowMagnificationManager; + private float mScale = 1.0f; + private float mCenterX = 0; + private float mCenterY = 0; + + WindowMagnificationManagerStub( + WindowMagnificationManager windowMagnificationManager) { + mWindowMagnificationManager = windowMagnificationManager; + } + + private void stubMethods() { + doAnswer(invocation -> mScale).when(mWindowMagnificationManager).getScale( + TEST_DISPLAY); + doAnswer(invocation -> mCenterX).when(mWindowMagnificationManager).getCenterX( + TEST_DISPLAY); + doAnswer(invocation -> mCenterY).when(mWindowMagnificationManager).getCenterY( + TEST_DISPLAY); + Answer enableWindowMagnificationStubAnswer = invocation -> { + mScale = invocation.getArgument(1); + mCenterX = invocation.getArgument(2); + mCenterY = invocation.getArgument(3); + return true; + }; + doAnswer(enableWindowMagnificationStubAnswer).when( + mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY), + anyFloat(), anyFloat(), anyFloat()); + } + + public void resetAndStubMethods() { + Mockito.reset(mWindowMagnificationManager); + stubMethods(); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index 15ba358f146e..02c0aca5c777 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -377,6 +377,17 @@ public class WindowMagnificationManagerTest { } @Test + public void resetMagnification_enabled_windowMagnifierDisabled() { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); + assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + + mWindowMagnificationManager.reset(TEST_DISPLAY); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + } + + @Test public void onScreenOff_windowMagnifierIsEnabled_removeButtonAndDisableWindowMagnification() throws RemoteException { mWindowMagnificationManager.requestConnection(true); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java index 7aea392e37ec..6a85c8b2e2ba 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static com.android.compatibility.common.util.ShellUtils.runShellCommand; + import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.fail; @@ -27,11 +29,17 @@ import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; import android.annotation.Nullable; +import android.app.AppGlobals; import android.content.IIntentReceiver; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; import android.util.SparseArray; +import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.HexDump; @@ -42,6 +50,7 @@ import com.google.android.collect.Lists; import org.junit.After; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -62,6 +71,11 @@ import java.util.regex.Pattern; // bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest @RunWith(AndroidJUnit4.class) public class PackageManagerServiceTest { + + private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/"; + private static final String TEST_APP_APK = "StubTestApp.apk"; + private static final String TEST_PKG_NAME = "com.android.servicestests.apps.stubapp"; + @Before public void setUp() throws Exception { } @@ -603,4 +617,119 @@ public class PackageManagerServiceTest { Collections.sort(knownPackageIds); return knownPackageIds; } + + @Test + public void testInstallReason_afterUpdate_keepUnchanged() throws Exception { + final IPackageManager pm = AppGlobals.getPackageManager(); + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + try { + // Try to install test APK with reason INSTALL_REASON_POLICY + runShellCommand("pm install --install-reason 1 " + testApk); + assertWithMessage("The install reason of test APK is incorrect.").that( + pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( + PackageManager.INSTALL_REASON_POLICY); + + // Try to update test APK with different reason INSTALL_REASON_USER + runShellCommand("pm install --install-reason 4 " + testApk); + assertWithMessage("The install reason should keep unchanged after update.").that( + pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( + PackageManager.INSTALL_REASON_POLICY); + } finally { + runShellCommand("pm uninstall " + TEST_PKG_NAME); + } + } + + @Test + public void testInstallReason_userRemainsUninstalled_keepUnknown() throws Exception { + Assume.assumeTrue(UserManager.supportsMultipleUsers()); + final IPackageManager pm = AppGlobals.getPackageManager(); + final UserManager um = UserManager.get( + InstrumentationRegistry.getInstrumentation().getContext()); + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + int userId = UserHandle.USER_NULL; + try { + // Try to install test APK with reason INSTALL_REASON_POLICY + runShellCommand("pm install --install-reason 1 " + testApk); + assertWithMessage("The install reason of test APK is incorrect.").that( + pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( + PackageManager.INSTALL_REASON_POLICY); + + // Create and start the 2nd user. + userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier(); + runShellCommand("am start-user -w " + userId); + // Since the test APK isn't installed on the 2nd user, the reason should be unknown. + assertWithMessage("The install reason in 2nd user should be unknown.").that( + pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( + PackageManager.INSTALL_REASON_UNKNOWN); + + // Try to update test APK with different reason INSTALL_REASON_USER + runShellCommand("pm install --install-reason 4 " + testApk); + assertWithMessage("The install reason in 2nd user should keep unknown.").that( + pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( + PackageManager.INSTALL_REASON_UNKNOWN); + } finally { + runShellCommand("pm uninstall " + TEST_PKG_NAME); + if (userId != UserHandle.USER_NULL) { + um.removeUser(userId); + } + } + } + + @Test + public void testInstallReason_installForAllUsers_sameReason() throws Exception { + Assume.assumeTrue(UserManager.supportsMultipleUsers()); + final IPackageManager pm = AppGlobals.getPackageManager(); + final UserManager um = UserManager.get( + InstrumentationRegistry.getInstrumentation().getContext()); + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + int userId = UserHandle.USER_NULL; + try { + // Create and start the 2nd user. + userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier(); + runShellCommand("am start-user -w " + userId); + + // Try to install test APK to all users with reason INSTALL_REASON_POLICY + runShellCommand("pm install --install-reason 1 " + testApk); + assertWithMessage("The install reason is inconsistent across users.").that( + pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( + pm.getInstallReason(TEST_PKG_NAME, userId)); + } finally { + runShellCommand("pm uninstall " + TEST_PKG_NAME); + if (userId != UserHandle.USER_NULL) { + um.removeUser(userId); + } + } + } + + @Test + public void testInstallReason_installSeparately_withSeparatedReason() throws Exception { + Assume.assumeTrue(UserManager.supportsMultipleUsers()); + final IPackageManager pm = AppGlobals.getPackageManager(); + final UserManager um = UserManager.get( + InstrumentationRegistry.getInstrumentation().getContext()); + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + int userId = UserHandle.USER_NULL; + try { + // Create and start the 2nd user. + userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier(); + runShellCommand("am start-user -w " + userId); + + // Try to install test APK on the current user with reason INSTALL_REASON_POLICY + runShellCommand("pm install --user cur --install-reason 1 " + testApk); + assertWithMessage("The install reason on the current user is incorrect.").that( + pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( + PackageManager.INSTALL_REASON_POLICY); + + // Try to install test APK on the 2nd user with reason INSTALL_REASON_USER + runShellCommand("pm install --user " + userId + " --install-reason 4 " + testApk); + assertWithMessage("The install reason on the 2nd user is incorrect.").that( + pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( + PackageManager.INSTALL_REASON_USER); + } finally { + runShellCommand("pm uninstall " + TEST_PKG_NAME); + if (userId != UserHandle.USER_NULL) { + um.removeUser(userId); + } + } + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 5d93e3dfadde..ab37e9bf6deb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -25,6 +25,7 @@ import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND; import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; import static android.content.res.Resources.ID_NULL; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; @@ -86,7 +87,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -1004,6 +1004,13 @@ public class PackageManagerSettingsTests { } } + private void verifyKeySetData(PackageKeySetData originalData, PackageKeySetData testData) { + assertThat(originalData.getProperSigningKeySet(), + equalTo(testData.getProperSigningKeySet())); + assertThat(originalData.getUpgradeKeySets(), is(testData.getUpgradeKeySets())); + assertThat(originalData.getAliases(), is(testData.getAliases())); + } + private void verifySettingCopy(PackageSetting origPkgSetting, PackageSetting testPkgSetting) { assertThat(origPkgSetting, is(not(testPkgSetting))); assertThat(origPkgSetting.getAppId(), is(testPkgSetting.getAppId())); @@ -1018,8 +1025,7 @@ public class PackageManagerSettingsTests { assertSame(origPkgSetting.getInstallSource(), testPkgSetting.getInstallSource()); assertThat(origPkgSetting.isInstallPermissionsFixed(), is(testPkgSetting.isInstallPermissionsFixed())); - assertSame(origPkgSetting.getKeySetData(), testPkgSetting.getKeySetData()); - assertThat(origPkgSetting.getKeySetData(), is(testPkgSetting.getKeySetData())); + verifyKeySetData(origPkgSetting.getKeySetData(), testPkgSetting.getKeySetData()); assertThat(origPkgSetting.getLastUpdateTime(), is(testPkgSetting.getLastUpdateTime())); assertSame(origPkgSetting.getLegacyNativeLibraryPath(), testPkgSetting.getLegacyNativeLibraryPath()); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index 7d24a2f5845e..a9cbad27c4c5 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -16,6 +16,19 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.USAGE_ALARM; +import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST; +import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK; +import static android.os.VibrationAttributes.USAGE_NOTIFICATION; +import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION; +import static android.os.VibrationAttributes.USAGE_RINGTONE; +import static android.os.VibrationAttributes.USAGE_TOUCH; +import static android.os.VibrationAttributes.USAGE_UNKNOWN; +import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH; +import static android.os.Vibrator.VIBRATION_INTENSITY_LOW; +import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM; +import static android.os.Vibrator.VIBRATION_INTENSITY_OFF; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -39,9 +52,7 @@ import android.os.Handler; import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.UserHandle; -import android.os.VibrationAttributes; import android.os.VibrationEffect; -import android.os.Vibrator; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.provider.Settings; @@ -70,16 +81,19 @@ import org.mockito.junit.MockitoRule; public class VibrationSettingsTest { private static final int UID = 1; - private static final int USER_OPERATION_TIMEOUT_MILLIS = 60_000; // 1 min private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() .setBatterySaverEnabled(true).build(); - @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); - @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock; - @Mock private PowerManagerInternal mPowerManagerInternalMock; + @Mock + private VibrationSettings.OnVibratorSettingsChanged mListenerMock; + @Mock + private PowerManagerInternal mPowerManagerInternalMock; private TestLooper mTestLooper; private ContextWrapper mContextSpy; @@ -129,14 +143,12 @@ public class VibrationSettingsTest { setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); - verify(mListenerMock, times(7)).onChange(); + verify(mListenerMock, times(8)).onChange(); } @Test @@ -171,89 +183,83 @@ public class VibrationSettingsTest { VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy, new Handler(mTestLooper.getLooper())); - assertFalse(vibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_RINGTONE)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_NOTIFICATION)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); + assertFalse(vibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK)); } @Test public void shouldVibrateForRingerMode_withoutRingtoneUsage_returnsTrue() { - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_NOTIFICATION)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK)); } @Test public void shouldVibrateForRingerMode_withVibrateWhenRinging_ignoreSettingsForSilentMode() { - int usageRingtone = VibrationAttributes.USAGE_RINGTONE; setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); setRingerMode(AudioManager.RINGER_MODE_SILENT); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_MAX); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_NORMAL); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_VIBRATE); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); } @Test public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() { - int usageRingtone = VibrationAttributes.USAGE_RINGTONE; setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); setRingerMode(AudioManager.RINGER_MODE_SILENT); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_MAX); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_NORMAL); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_VIBRATE); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); } @Test public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() { - int usageRingtone = VibrationAttributes.USAGE_RINGTONE; setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); setRingerMode(AudioManager.RINGER_MODE_VIBRATE); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_SILENT); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_MAX); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_NORMAL); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); } @Test public void shouldVibrateForUid_withForegroundOnlyUsage_returnsTrueWhInForeground() { - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH)); mVibrationSettings.mUidObserver.onUidStateChanged( UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); - assertFalse(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH)); + assertFalse(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH)); } @Test @@ -261,38 +267,32 @@ public class VibrationSettingsTest { mVibrationSettings.mUidObserver.onUidStateChanged( UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_ALARM)); - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, - VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, - VibrationAttributes.USAGE_NOTIFICATION)); - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_RINGTONE)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_RINGTONE)); } @Test public void shouldVibrateForPowerMode_withLowPowerAndAllowedUsage_returnTrue() { mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_ALARM)); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode( - VibrationAttributes.USAGE_RINGTONE)); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode( - VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_RINGTONE)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_COMMUNICATION_REQUEST)); } @Test public void shouldVibrateForPowerMode_withRestrictedUsage_returnsFalseWhileInLowPowerMode() { mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH)); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode( - VibrationAttributes.USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION)); mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - assertFalse(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH)); - assertFalse(mVibrationSettings.shouldVibrateForPowerMode( - VibrationAttributes.USAGE_NOTIFICATION)); + assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH)); + assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION)); } @Test @@ -324,108 +324,128 @@ public class VibrationSettingsTest { @Test public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() { - mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); - mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_HIGH); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy, new Handler(mTestLooper.getLooper())); - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity( - VibrationAttributes.USAGE_PHYSICAL_EMULATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + assertEquals(VIBRATION_INTENSITY_HIGH, + vibrationSettings.getDefaultIntensity(USAGE_ALARM)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_TOUCH)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_UNKNOWN)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_RINGTONE)); } @Test public void getDefaultIntensity_returnsIntensityFromVibratorService() { - mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM); - mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW); - - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM)); - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getDefaultIntensity( - VibrationAttributes.USAGE_PHYSICAL_EMULATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_LOW, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_MEDIUM); + mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_LOW); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(USAGE_ALARM)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(USAGE_TOUCH)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getDefaultIntensity(USAGE_UNKNOWN)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getDefaultIntensity(USAGE_RINGTONE)); } @Test public void getCurrentIntensity_returnsIntensityFromSettings() { - mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_OFF); - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF); - mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF); + mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_OFF); + mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_OFF); + mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH); + setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_LOW); - - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_ALARM)); - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_UNKNOWN)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getCurrentIntensity( - VibrationAttributes.USAGE_PHYSICAL_EMULATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_LOW, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); + VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW); + + assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_ALARM)); + assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(USAGE_NOTIFICATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(USAGE_UNKNOWN)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); } @Test public void getCurrentIntensity_updateTriggeredAfterUserSwitched() { - mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); + mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); + + // Switching user is not working with FakeSettingsProvider. + // Testing the broadcast flow manually. + Settings.System.putIntForUser(mContextSpy.getContentResolver(), + Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW, + UserHandle.USER_CURRENT); + mVibrationSettings.mUserReceiver.onReceive(mContextSpy, + new Intent(Intent.ACTION_USER_SWITCHED)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); + } + + @Test + public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() { + mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); + // If haptic feedback is off, fallback to default value. + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); // Switching user is not working with FakeSettingsProvider. // Testing the broadcast flow manually. Settings.System.putIntForUser(mContextSpy.getContentResolver(), - Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW, + Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH, UserHandle.USER_CURRENT); mVibrationSettings.mUserReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED)); - assertEquals(Vibrator.VIBRATION_INTENSITY_LOW, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); } @Test diff --git a/services/tests/servicestests/test-apps/StubApp/Android.bp b/services/tests/servicestests/test-apps/StubApp/Android.bp new file mode 100644 index 000000000000..99deb3f5bbf0 --- /dev/null +++ b/services/tests/servicestests/test-apps/StubApp/Android.bp @@ -0,0 +1,37 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "StubTestApp", + + sdk_version: "current", + + srcs: ["**/*.java"], + + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, +} diff --git a/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml new file mode 100644 index 000000000000..90172e77f958 --- /dev/null +++ b/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.stubapp"> + + <application android:label="StubTestApp"> + <activity android:name=".TestActivity" + android:exported="true" /> + </application> + +</manifest>
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java b/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java new file mode 100644 index 000000000000..0d94676aeb52 --- /dev/null +++ b/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java @@ -0,0 +1,22 @@ +/* + * 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 com.android.servicestests.apps.stubapp; + +import android.app.Activity; + +public class TestActivity extends Activity { +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 58089648ceca..ea3a4cd865cb 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -8511,6 +8511,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + // using the style, but incorrect type in session - blocked + nb.setStyle(new Notification.MediaStyle()); + Bundle extras = new Bundle(); + extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, new Intent()); + nb.addExtras(extras); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + // style + media session - bypasses block nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index ea0196306d9f..29ef339a6382 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -89,6 +89,7 @@ import android.media.AudioManager; import android.media.session.MediaSession; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Process; @@ -746,6 +747,18 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + // using the style, but incorrect type in session - blocked + nb.setStyle(new Notification.MediaStyle()); + Bundle extras = new Bundle(); + extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, new Intent()); + nb.addExtras(extras); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + // style + media session - bypasses block nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 1266b2e30859..afc2b8748abb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -854,12 +854,19 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + // committing empty locales, when no config is set should return false. + assertFalse(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit()); + // committing new configuration returns true; assertTrue(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) .commit()); // applying the same configuration returns false. assertFalse(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) .commit()); + + // committing empty locales and undefined nightMode should return true (deletes the + // pre-existing record) if some config was previously set. assertTrue(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()) .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 5d0e34a80f3f..6fa306b004a2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -803,6 +803,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord openingActivity = taskFragment.getTopMostActivity(); openingActivity.allDrawn = true; + task.effectiveUid = openingActivity.getUid(); spyOn(mDisplayContent.mAppTransition); // Prepare a transition. @@ -879,6 +880,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { final ActivityRecord closingActivity = taskFragment.getTopMostActivity(); closingActivity.allDrawn = true; closingActivity.info.applicationInfo.uid = 12345; + task.effectiveUid = closingActivity.getUid(); // Opening non-embedded activity with different UID. final ActivityRecord openingActivity = createActivityRecord(task); openingActivity.info.applicationInfo.uid = 54321; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 7e5414e8a5c2..a5c6dc0414dd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -352,28 +353,66 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() { + public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() + throws RemoteException { + mController.registerOrganizer(mIOrganizer); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final int uid = Binder.getCallingUid(); + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid; + final IBinder fragmentToken = new Binder(); + final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( + mOrganizerToken, fragmentToken, activity.token).build(); mOrganizer.applyTransaction(mTransaction); // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. - final TaskFragmentCreationParams mockParams = mock(TaskFragmentCreationParams.class); - doReturn(mOrganizerToken).when(mockParams).getOrganizer(); - mTransaction.createTaskFragment(mockParams); + mTransaction.createTaskFragment(params); mTransaction.startActivityInTaskFragment( mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */); mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class)); mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class), null /* options */); + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - // It is expected to fail for the mock TaskFragmentCreationParams. It is ok as we are - // testing the security check here. - assertThrows(IllegalArgumentException.class, () -> { - try { - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - } catch (RemoteException e) { - fail(); - } - }); + // Successfully created a TaskFragment. + final TaskFragment taskFragment = mAtm.mWindowOrganizerController + .getTaskFragment(fragmentToken); + assertNotNull(taskFragment); + assertEquals(activity.getTask(), taskFragment.getTask()); + } + + @Test + public void testApplyTransaction_createTaskFragment_failForDifferentUid() + throws RemoteException { + mController.registerOrganizer(mIOrganizer); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final int uid = Binder.getCallingUid(); + final IBinder fragmentToken = new Binder(); + final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( + mOrganizerToken, fragmentToken, activity.token).build(); + mOrganizer.applyTransaction(mTransaction); + mTransaction.createTaskFragment(params); + + // Fail to create TaskFragment when the task uid is different from caller. + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid + 1; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + + // Fail to create TaskFragment when the task uid is different from owner activity. + activity.info.applicationInfo.uid = uid + 1; + activity.getTask().effectiveUid = uid; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + + // Successfully created a TaskFragment for same uid. + activity.info.applicationInfo.uid = uid; + activity.getTask().effectiveUid = uid; + mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + + assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index f366f57bae08..caaf4e495e63 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -54,7 +54,6 @@ import android.view.RoundedCorners; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; -import android.window.ITransitionPlayer; import androidx.test.filters.SmallTest; @@ -313,11 +312,7 @@ public class WallpaperControllerTests extends WindowTestsBase { wallpaperWindow.setHasSurface(true); // Set-up mock shell transitions - final IBinder mockBinder = mock(IBinder.class); - final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class); - doReturn(mockBinder).when(mockPlayer).asBinder(); - mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer, - null /* appThread */); + registerTestTransitionPlayer(); Transition transit = mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN); @@ -338,10 +333,21 @@ public class WallpaperControllerTests extends WindowTestsBase { assertFalse(token.isVisibleRequested()); assertTrue(token.isVisible()); - transit.onTransactionReady(transit.getSyncId(), mock(SurfaceControl.Transaction.class)); - transit.finishTransition(); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + token.finishSync(t, false /* cancel */); + transit.onTransactionReady(transit.getSyncId(), t); + dc.mTransitionController.finishTransition(transit); assertFalse(wallpaperWindow.isVisible()); assertFalse(token.isVisible()); + + // Assume wallpaper was visible. When transaction is ready without wallpaper target, + // wallpaper should be requested to be invisible. + token.setVisibility(true); + transit = dc.mTransitionController.createTransition(TRANSIT_CLOSE); + dc.mTransitionController.collect(token); + transit.onTransactionReady(transit.getSyncId(), t); + assertFalse(token.isVisibleRequested()); + assertTrue(token.isVisible()); } private WindowState createWallpaperTargetWindow(DisplayContent dc) { diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index abbce1cd869d..98f619f497d4 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1687,7 +1687,11 @@ public class TelecomManager { * * @param accountHandle The handle for the account retrieve a number for. * @return A string representation of the line 1 phone number. + * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead, which takes a + * Telephony Subscription ID that can be retrieved with the {@code accountHandle} + * from {@link TelephonyManager#getSubscriptionId(PhoneAccountHandle)}. */ + @Deprecated @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges or default SMS app @RequiresPermission(anyOf = { android.Manifest.permission.READ_PHONE_STATE, diff --git a/telephony/common/Android.bp b/telephony/common/Android.bp index 1cacc0365fe1..b0a812bf57a3 100644 --- a/telephony/common/Android.bp +++ b/telephony/common/Android.bp @@ -21,7 +21,10 @@ filegroup { filegroup { name: "framework-mms-shared-srcs", - visibility: ["//packages/apps/Bluetooth"], + visibility: [ + "//packages/apps/Bluetooth", + "//packages/modules/Bluetooth/android/app", + ], srcs: [ "com/google/android/mms/**/*.java", ], diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index 7d857a2dc1b4..fabe612743bb 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -856,9 +856,10 @@ public abstract class EuiccService extends Service { mExecutor.execute(new Runnable() { @Override public void run() { + // TODO(b/207392528: use portIndex API once implemented) int result = - EuiccService.this.onSwitchToSubscriptionWithPort( - slotId, portIndex, iccid, forceDeactivateSim); + EuiccService.this.onSwitchToSubscription( + slotId, iccid, forceDeactivateSim); try { callback.onComplete(result); } catch (RemoteException e) { diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index 23cf5116b2da..e88106cb95fe 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -1,6 +1,8 @@ package android.telephony; import android.annotation.IntDef; +import android.net.NetworkAgent; +import android.net.NetworkCapabilities; import android.telecom.Connection; import android.telephony.data.ApnSetting; @@ -664,4 +666,59 @@ public class Annotation { TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE, TelephonyManager.THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR}) public @interface ThermalMitigationResult {} + + /** + * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate + * android.net.NetworkCapabilities.NetCapability here. Must update here when new capabilities + * are added in {@link NetworkCapabilities}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "NET_CAPABILITY_" }, value = { + NetworkCapabilities.NET_CAPABILITY_MMS, + NetworkCapabilities.NET_CAPABILITY_SUPL, + NetworkCapabilities.NET_CAPABILITY_DUN, + NetworkCapabilities.NET_CAPABILITY_FOTA, + NetworkCapabilities.NET_CAPABILITY_IMS, + NetworkCapabilities.NET_CAPABILITY_CBS, + NetworkCapabilities.NET_CAPABILITY_WIFI_P2P, + NetworkCapabilities.NET_CAPABILITY_IA, + NetworkCapabilities.NET_CAPABILITY_RCS, + NetworkCapabilities.NET_CAPABILITY_XCAP, + NetworkCapabilities.NET_CAPABILITY_EIMS, + NetworkCapabilities.NET_CAPABILITY_NOT_METERED, + NetworkCapabilities.NET_CAPABILITY_INTERNET, + NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED, + NetworkCapabilities.NET_CAPABILITY_TRUSTED, + NetworkCapabilities.NET_CAPABILITY_NOT_VPN, + NetworkCapabilities.NET_CAPABILITY_VALIDATED, + NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL, + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, + NetworkCapabilities.NET_CAPABILITY_FOREGROUND, + NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED, + NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED, + NetworkCapabilities.NET_CAPABILITY_OEM_PAID, + NetworkCapabilities.NET_CAPABILITY_MCX, + NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED, + NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, + NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL, + NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED, + NetworkCapabilities.NET_CAPABILITY_ENTERPRISE, + NetworkCapabilities.NET_CAPABILITY_VSIM, + NetworkCapabilities.NET_CAPABILITY_BIP, + NetworkCapabilities.NET_CAPABILITY_HEAD_UNIT, + }) + public @interface NetCapability { } + + /** + * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate + * android.net.NetworkAgent.ValidationStatus here. Must update here when new validation status + * are added in {@link NetworkAgent}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "VALIDATION_STATUS_" }, value = { + NetworkAgent.VALIDATION_STATUS_VALID, + NetworkAgent.VALIDATION_STATUS_NOT_VALID + }) + public @interface ValidationStatus {} } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 3f0f50c5f62a..c7e5aafe23e8 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5951,9 +5951,9 @@ public class CarrierConfigManager { "capabilities=eims, retry_interval=1000, maximum_retries=20", "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|" + "2254, maximum_retries=0", // No retry for those causes + "capabilities=mms|supl|cbs, retry_interval=2000", "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2000, " + "backoff=true, maximum_retries=13", - "capabilities=mms|supl|cbs, retry_interval=2000" }); sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index 88efe1f6a4a7..56bf3039d209 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -1076,6 +1076,13 @@ public final class DataFailCause { */ public static final int SERVICE_TEMPORARILY_UNAVAILABLE = 0x10009; + /** + * The request is not supported by the vendor. + * + * @hide + */ + public static final int REQUEST_NOT_SUPPORTED = 0x1000A; + private static final Map<Integer, String> sFailCauseMap; static { sFailCauseMap = new HashMap<>(); diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java index 2ff4ac5e30d3..9cb80f1814f9 100644 --- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java +++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java @@ -71,12 +71,7 @@ public final class SignalStrengthUpdateRequest implements Parcelable { @Nullable List<SignalThresholdInfo> signalThresholdInfos, boolean isReportingRequestedWhileIdle, boolean isSystemThresholdReportingRequestedWhileIdle) { - // System app (like Bluetooth) can specify the request to report system thresholds while - // device is idle (with permission protection). In this case, the request doesn't need to - // provide a non-empty list of SignalThresholdInfo which is only asked for public apps. - if (!isSystemThresholdReportingRequestedWhileIdle) { - validate(signalThresholdInfos); - } + validate(signalThresholdInfos, isSystemThresholdReportingRequestedWhileIdle); mSignalThresholdInfos = signalThresholdInfos; mIsReportingRequestedWhileIdle = isReportingRequestedWhileIdle; @@ -274,8 +269,12 @@ public final class SignalStrengthUpdateRequest implements Parcelable { * Throw IAE if SignalThresholdInfo collection is null or empty, * or the SignalMeasurementType for the same RAN in the collection is not unique. */ - private static void validate(Collection<SignalThresholdInfo> infos) { - if (infos == null || infos.isEmpty()) { + private static void validate(Collection<SignalThresholdInfo> infos, + boolean isSystemThresholdReportingRequestedWhileIdle) { + // System app (like Bluetooth) can specify the request to report system thresholds while + // device is idle (with permission protection). In this case, the request doesn't need to + // provide a non-empty list of SignalThresholdInfo which is only asked for public apps. + if (infos == null || (infos.isEmpty() && !isSystemThresholdReportingRequestedWhileIdle)) { throw new IllegalArgumentException("SignalThresholdInfo collection is null or empty"); } diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index d6d6775cb40f..d11ad914061d 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -498,7 +498,10 @@ public class SubscriptionInfo implements Parcelable { * * @return the number of this subscription, or an empty string if one of these requirements is * not met + * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead, which takes a + * {@link #getSubscriptionId() subscription ID}. */ + @Deprecated public String getNumber() { return mNumber; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 04e7b7c8711c..88b21e0428b0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4791,7 +4791,10 @@ public class TelephonyManager { * for any API level. * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} * for apps targeting SDK API level 29 and below. + * + * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead. */ + @Deprecated @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges or default SMS app @RequiresPermission(anyOf = { android.Manifest.permission.READ_PHONE_STATE, @@ -4864,7 +4867,9 @@ public class TelephonyManager { * @param alphaTag alpha-tagging of the dailing nubmer * @param number The dialing number * @return true if the operation was executed correctly. + * @deprecated use {@link SubscriptionManager#setCarrierPhoneNumber(int, String)} instead. */ + @Deprecated public boolean setLine1NumberForDisplay(String alphaTag, String number) { return setLine1NumberForDisplay(getSubId(), alphaTag, number); } @@ -4885,6 +4890,10 @@ public class TelephonyManager { */ public boolean setLine1NumberForDisplay(int subId, String alphaTag, String number) { try { + // This API is deprecated; call the new API to allow smooth migartion. + // The new API doesn't accept null so convert null to empty string. + mSubscriptionManager.setCarrierPhoneNumber(subId, (number == null ? "" : number)); + ITelephony telephony = getITelephony(); if (telephony != null) return telephony.setLine1NumberForDisplayForSubscriber(subId, alphaTag, number); diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java index 93903d2658cd..43ad982f2e1e 100644 --- a/telephony/java/android/telephony/data/DataProfile.java +++ b/telephony/java/android/telephony/data/DataProfile.java @@ -22,9 +22,11 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.net.NetworkCapabilities; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Annotation.ApnType; +import android.telephony.Annotation.NetCapability; import android.telephony.TelephonyManager; import android.telephony.TelephonyManager.NetworkTypeBitMask; import android.telephony.data.ApnSetting.AuthType; @@ -66,7 +68,7 @@ public final class DataProfile implements Parcelable { private final @Nullable TrafficDescriptor mTrafficDescriptor; - private final boolean mPreferred; + private boolean mPreferred; private DataProfile(@NonNull Builder builder) { mApnSetting = builder.mApnSetting; @@ -291,6 +293,16 @@ public final class DataProfile implements Parcelable { } /** + * Set the preferred flag for the data profile. + * + * @param preferred {@code true} if this data profile is preferred for internet. + * @hide + */ + public void setPreferred(boolean preferred) { + mPreferred = preferred; + } + + /** * @return {@code true} if this data profile was used to bring up the last default * (i.e internet) data connection successfully, or the one chosen by the user in Settings' * APN editor. For one carrier there can be only one profiled preferred. @@ -315,6 +327,76 @@ public final class DataProfile implements Parcelable { return mTrafficDescriptor; } + /** + * Check if this data profile can satisfy certain network capabilities + * + * @param networkCapabilities The network capabilities. Note that the non-APN-type capabilities + * will be ignored. + * + * @return {@code true} if this data profile can satisfy the given network capabilities. + * @hide + */ + public boolean canSatisfy(@NonNull @NetCapability int[] networkCapabilities) { + if (mApnSetting != null) { + for (int netCap : networkCapabilities) { + if (!canSatisfy(netCap)) { + return false; + } + } + return true; + } + return false; + } + + /** + * Check if this data profile can satisfy a certain network capability. + * + * @param networkCapability The network capability. Note that the non-APN-type capability + * will always be satisfied. + * @return {@code true} if this data profile can satisfy the given network capability. + * @hide + */ + public boolean canSatisfy(@NetCapability int networkCapability) { + return mApnSetting != null && mApnSetting.canHandleType( + networkCapabilityToApnType(networkCapability)); + } + + /** + * Convert network capability into APN type. + * + * @param networkCapability Network capability. + * @return APN type. + * @hide + */ + private static @ApnType int networkCapabilityToApnType(@NetCapability int networkCapability) { + switch (networkCapability) { + case NetworkCapabilities.NET_CAPABILITY_MMS: + return ApnSetting.TYPE_MMS; + case NetworkCapabilities.NET_CAPABILITY_SUPL: + return ApnSetting.TYPE_SUPL; + case NetworkCapabilities.NET_CAPABILITY_DUN: + return ApnSetting.TYPE_DUN; + case NetworkCapabilities.NET_CAPABILITY_FOTA: + return ApnSetting.TYPE_FOTA; + case NetworkCapabilities.NET_CAPABILITY_IMS: + return ApnSetting.TYPE_IMS; + case NetworkCapabilities.NET_CAPABILITY_CBS: + return ApnSetting.TYPE_CBS; + case NetworkCapabilities.NET_CAPABILITY_XCAP: + return ApnSetting.TYPE_XCAP; + case NetworkCapabilities.NET_CAPABILITY_EIMS: + return ApnSetting.TYPE_EMERGENCY; + case NetworkCapabilities.NET_CAPABILITY_INTERNET: + return ApnSetting.TYPE_DEFAULT; + case NetworkCapabilities.NET_CAPABILITY_MCX: + return ApnSetting.TYPE_MCX; + case NetworkCapabilities.NET_CAPABILITY_IA: + return ApnSetting.TYPE_IA; + default: + return ApnSetting.TYPE_NONE; + } + } + @Override public int describeContents() { return 0; diff --git a/telephony/java/android/telephony/ims/DelegateRegistrationState.java b/telephony/java/android/telephony/ims/DelegateRegistrationState.java index c00c741a0d60..1b1040430fd2 100644 --- a/telephony/java/android/telephony/ims/DelegateRegistrationState.java +++ b/telephony/java/android/telephony/ims/DelegateRegistrationState.java @@ -117,6 +117,7 @@ public final class DelegateRegistrationState implements Parcelable { }) public @interface DeregisteringReason {} + private ArraySet<String> mRegisteringTags = new ArraySet<>(); private ArraySet<String> mRegisteredTags = new ArraySet<>(); private final ArraySet<FeatureTagState> mDeregisteringTags = new ArraySet<>(); private final ArraySet<FeatureTagState> mDeregisteredTags = new ArraySet<>(); @@ -134,6 +135,20 @@ public final class DelegateRegistrationState implements Parcelable { } /** + * Add the set of feature tags that are associated with this SipDelegate and + * the IMS stack is actively trying to register on the carrier network. + * + * The feature tags will either move to the registered or deregistered state + * depending on the result of the registration. + * @param featureTags The IMS media feature tags that are in the progress of registering. + * @return The in-progress Builder instance for RegistrationState. ] + */ + public @NonNull Builder addRegisteringFeatureTags(@NonNull Set<String> featureTags) { + mState.mRegisteringTags.addAll(featureTags); + return this; + } + + /** * Add a feature tag that is currently included in the current network IMS Registration. * @param featureTag The IMS media feature tag included in the current IMS registration. * @return The in-progress Builder instance for RegistrationState. @@ -209,6 +224,17 @@ public final class DelegateRegistrationState implements Parcelable { mRegisteredTags = (ArraySet<String>) source.readArraySet(null); readStateFromParcel(source, mDeregisteringTags); readStateFromParcel(source, mDeregisteredTags); + mRegisteringTags = (ArraySet<String>) source.readArraySet(null); + } + + /** + * Get the feature tags that are associated with this SipDelegate that the IMS stack is actively + * trying to register on the carrier network. + * @return A Set of feature tags associated with this SipDelegate that the IMS service is + * currently trying to register on the carrier network. + */ + public @NonNull Set<String> getRegisteringFeatureTags() { + return new ArraySet<>(mRegisteringTags); } /** @@ -286,6 +312,7 @@ public final class DelegateRegistrationState implements Parcelable { dest.writeArraySet(mRegisteredTags); writeStateToParcel(dest, mDeregisteringTags); writeStateToParcel(dest, mDeregisteredTags); + dest.writeArraySet(mRegisteringTags); } private void writeStateToParcel(Parcel dest, Set<FeatureTagState> state) { @@ -311,19 +338,22 @@ public final class DelegateRegistrationState implements Parcelable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DelegateRegistrationState that = (DelegateRegistrationState) o; - return mRegisteredTags.equals(that.mRegisteredTags) + return mRegisteringTags.equals(that.mRegisteringTags) + && mRegisteredTags.equals(that.mRegisteredTags) && mDeregisteringTags.equals(that.mDeregisteringTags) && mDeregisteredTags.equals(that.mDeregisteredTags); } @Override public int hashCode() { - return Objects.hash(mRegisteredTags, mDeregisteringTags, mDeregisteredTags); + return Objects.hash(mRegisteringTags, mRegisteredTags, + mDeregisteringTags, mDeregisteredTags); } @Override public String toString() { return "DelegateRegistrationState{ registered={" + mRegisteredTags + + "}, registering={" + mRegisteringTags + "}, deregistering={" + mDeregisteringTags + "}, deregistered={" + mDeregisteredTags + "}}"; } |